3 namespace Drupal\image_widget_crop;
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\Core\Entity\EntityTypeManagerInterface;
7 use Drupal\Core\Entity\FieldableEntityInterface;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\crop\Entity\Crop;
10 use Drupal\crop\Entity\CropType;
11 use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
12 use Drupal\image\Entity\ImageStyle;
15 * ImageWidgetCropManager calculation class.
17 class ImageWidgetCropManager implements ImageWidgetCropInterface {
20 * The entity type manager service.
22 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
24 protected $entityTypeManager;
29 * @var \Drupal\Core\Entity\EntityStorageInterface
31 protected $cropStorage;
36 * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
38 protected $cropTypeStorage;
41 * The image style storage.
43 * @var \Drupal\Core\Entity\EntityStorageInterface
45 protected $imageStyleStorage;
50 * @var \Drupal\Core\Entity\EntityStorageInterface
52 protected $fileStorage;
55 * Constructs a ImageWidgetCropManager object.
57 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
58 * Entity type manager service.
60 public function __construct(EntityTypeManagerInterface $entity_type_manager) {
61 $this->entityTypeManager = $entity_type_manager;
62 $this->cropStorage = $this->entityTypeManager->getStorage('crop');
63 $this->cropTypeStorage = $this->entityTypeManager->getStorage('crop_type');
64 $this->imageStyleStorage = $this->entityTypeManager->getStorage('image_style');
65 $this->fileStorage = $this->entityTypeManager->getStorage('file');
71 public function applyCrop(array $properties, $field_value, CropType $crop_type) {
72 // Get Original sizes and position of crop zone.
73 $crop_properties = $this->getCropOriginalDimension($field_value, $properties);
74 // Get all imagesStyle used this crop_type.
75 $image_styles = $this->getImageStylesByCrop($crop_type->id());
89 public function updateCrop(array $properties, $field_value, CropType $crop_type) {
90 // Get Original sizes and position of crop zone.
91 $crop_properties = $this->getCropOriginalDimension($field_value, $properties);
93 // Get all imagesStyle used this crop_type.
94 $image_styles = $this->getImageStylesByCrop($crop_type->id());
96 if (!empty($image_styles)) {
97 $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $field_value['file-uri']);
100 // If any crop exist add new crop.
102 $this->saveCrop($crop_properties, $field_value, $image_styles, $crop_type);
106 /** @var \Drupal\crop\Entity\Crop $crop */
107 foreach ($crops as $crop) {
108 if (!$this->cropHasChanged($crop_properties, array_merge($crop->position(), $crop->size()))) {
112 $this->updateCropProperties($crop, $crop_properties);
113 drupal_set_message(t('The crop "@cropType" were successfully updated for image "@filename".', ['@cropType' => $crop_type->label(), '@filename' => $this->fileStorage->load($field_value['file-id'])->getFilename()]));
120 public function saveCrop(array $crop_properties, $field_value, array $image_styles, CropType $crop_type, $notify = TRUE) {
122 'type' => $crop_type->id(),
123 'entity_id' => $field_value['file-id'],
124 'entity_type' => 'file',
125 'uri' => $field_value['file-uri'],
126 'x' => $crop_properties['x'],
127 'y' => $crop_properties['y'],
128 'width' => $crop_properties['width'],
129 'height' => $crop_properties['height'],
132 // Save crop with previous values.
133 /** @var \Drupal\crop\CropInterface $crop */
134 $crop = $this->cropStorage->create($values);
138 drupal_set_message(t('The crop "@cropType" was successfully added for image "@filename".', ['@cropType' => $crop_type->label(), '@filename' => $this->fileStorage->load($field_value['file-id'])->getFilename()]));
145 public function deleteCrop($file_uri, CropType $crop_type, $file_id) {
146 $image_styles = $this->getImageStylesByCrop($crop_type->id());
147 $crop = $this->cropStorage->loadByProperties([
148 'type' => $crop_type->id(),
151 $this->cropStorage->delete($crop);
152 $this->imageStylesOperations($image_styles, $file_uri);
153 drupal_set_message(t('The crop "@cropType" was successfully deleted for image "@filename".', [
154 '@cropType' => $crop_type->label(),
155 '@filename' => $this->fileStorage->load($file_id)->getFilename(),
162 public function getAxisCoordinates(array $axis, array $crop_selection) {
164 'x' => (int) round($axis['x'] + ($crop_selection['width'] / 2)),
165 'y' => (int) round($axis['y'] + ($crop_selection['height'] / 2)),
172 public function getCropOriginalDimension(array $field_values, array $properties) {
173 $crop_coordinates = [];
175 /** @var \Drupal\Core\Image\Image $image */
176 $image = \Drupal::service('image.factory')->get($field_values['file-uri']);
177 if (!$image->isValid()) {
178 drupal_set_message(t('The file "@file" is not valid, your crop is not applied.', [
179 '@file' => $field_values['file-uri'],
184 // Get Center coordinate of crop zone on original image.
185 $axis_coordinate = $this->getAxisCoordinates(
186 ['x' => $properties['x'], 'y' => $properties['y']],
187 ['width' => $properties['width'], 'height' => $properties['height']]
190 // Calculate coordinates (position & sizes) of crop zone on original image.
191 $crop_coordinates['width'] = $properties['width'];
192 $crop_coordinates['height'] = $properties['height'];
193 $crop_coordinates['x'] = $axis_coordinate['x'];
194 $crop_coordinates['y'] = $axis_coordinate['y'];
196 return $crop_coordinates;
202 public function getEffectData(ImageStyle $image_style, $data_type) {
204 /* @var \Drupal\image\ImageEffectInterface $effect */
205 foreach ($image_style->getEffects() as $uuid => $effect) {
206 $data_effect = $image_style->getEffect($uuid)->getConfiguration()['data'];
207 if (isset($data_effect[$data_type])) {
208 $data = $data_effect[$data_type];
218 public function getImageStylesByCrop($crop_type_name) {
220 $image_styles = $this->imageStyleStorage->loadMultiple();
222 /** @var \Drupal\image\Entity\ImageStyle $image_style */
223 foreach ($image_styles as $image_style) {
224 $image_style_data = $this->getEffectData($image_style, 'crop_type');
225 if (!empty($image_style_data) && ($image_style_data == $crop_type_name)) {
226 $styles[] = $image_style;
236 public function imageStylesOperations(array $image_styles, $file_uri, $create_derivative = FALSE) {
237 /** @var \Drupal\image\Entity\ImageStyle $image_style */
238 foreach ($image_styles as $image_style) {
239 if ($create_derivative) {
240 // Generate the image derivative uri.
241 $destination_uri = $image_style->buildUri($file_uri);
243 // Create a derivative of the original image with a good uri.
244 $image_style->createDerivative($file_uri, $destination_uri);
246 // Flush the cache of this ImageStyle.
247 $image_style->flush($file_uri);
254 public function updateCropProperties(Crop $crop, array $crop_properties) {
255 // Parse all properties if this crop have changed.
256 foreach ($crop_properties as $crop_coordinate => $value) {
257 // Edit the crop properties if he have changed.
258 $crop->set($crop_coordinate, $value, TRUE);
267 public function loadImageStyleByCrop(array $image_styles, CropType $crop_type, $file_uri) {
269 /** @var \Drupal\image\Entity\ImageStyle $image_style */
270 foreach ($image_styles as $image_style) {
271 /** @var \Drupal\crop\Entity\Crop $crop */
272 $crop = Crop::findCrop($file_uri, $crop_type->id());
274 $crops[$image_style->id()] = $crop;
284 public function cropHasChanged(array $crop_properties, array $old_crop) {
285 if (!empty(array_diff_assoc($crop_properties, $old_crop))) {
294 public function getAvailableCropImageStyle(array $styles) {
295 $available_styles = [];
296 foreach ($styles as $style_id => $style_label) {
297 $style_loaded = $this->imageStyleStorage->loadByProperties(['name' => $style_id]);
298 /** @var \Drupal\image\Entity\ImageStyle $image_style */
299 $image_style = $style_loaded[$style_id];
300 $effect_data = $this->getEffectData($image_style, 'width');
301 if (!empty($effect_data)) {
302 $available_styles[$style_id] = $style_label;
306 return $available_styles;
312 public function getAvailableCropType(array $crop_list) {
313 $available_crop = [];
314 foreach ($crop_list as $crop_machine_name => $crop_label) {
315 $image_styles = $this->getImageStylesByCrop($crop_machine_name);
316 if (!empty($image_styles)) {
317 $available_crop[$crop_machine_name] = $crop_label;
321 return $available_crop;
327 public static function getCropProperties(Crop $crop) {
328 $anchor = $crop->anchor();
329 $size = $crop->size();
333 'height' => $size['height'],
334 'width' => $size['width'],
341 public function buildCropToEntity(EntityInterface $entity) {
342 if (isset($entity) && $entity instanceof FieldableEntityInterface) {
343 // Loop all fields of the saved entity.
344 foreach ($entity->getFields() as $entity_fields) {
345 // If current field is FileField and use imageWidgetCrop.
346 if ($entity_fields instanceof FileFieldItemList) {
347 /* First loop to get each elements independently in the field values.
348 Required if the image field cardinality > 1. */
349 foreach ($entity_fields->getValue() as $crop_elements) {
350 foreach ($crop_elements as $crop_element) {
351 if (is_array($crop_element) && isset($crop_element['crop_wrapper'])) {
352 // Reload image since its URI could have been changed,
354 /** @var \Drupal\file_entity\Entity\FileEntity $file */
355 $file = $this->fileStorage->load($crop_element['file-id']);
356 $crop_element['file-uri'] = $file->getFileUri();
357 // Parse all value of a crop_wrapper element and get properties
358 // associate with her CropType.
359 foreach ($crop_element['crop_wrapper'] as $crop_type_name => $properties) {
360 $properties = $properties['crop_container']['values'];
361 /** @var \Drupal\crop\Entity\CropType $crop_type */
362 $crop_type = $this->cropTypeStorage->load($crop_type_name);
364 // If the crop type needed is disabled or delete.
365 if (empty($crop_type) && $crop_type instanceof CropType) {
366 drupal_set_message(t("The CropType ('@cropType') is not active or not defined. Please verify configuration of image style or ImageWidgetCrop formatter configuration", ['@cropType' => $crop_type->id()]), 'error');
370 // If this crop is available to create an crop entity.
371 if ($entity->isNew()) {
372 if ($properties['crop_applied'] == '1' && isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
373 $this->applyCrop($properties, $crop_element, $crop_type);
377 // Get all imagesStyle used this crop_type.
378 $image_styles = $this->getImageStylesByCrop($crop_type_name);
379 $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $crop_element['file-uri']);
380 // If the entity already exist & is not deleted by user
381 // update $crop_type_name crop entity.
382 if ($properties['crop_applied'] == '0' && !empty($crops)) {
383 $this->deleteCrop($crop_element['file-uri'], $crop_type, $crop_element['file-id']);
385 elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
386 $this->updateCrop($properties, $crop_element, $crop_type);
401 public function buildCropToForm(FormStateInterface $form_state) {
402 /** @var \Drupal\file_entity\Entity\FileEntity $entity */
403 if ($entity = $form_state->getFormObject()->getEntity()) {
404 $form_state_values = $form_state->getValues();
405 // @TODO Do not hardcode key of element to "image_crop" check #type of element instead like \Drupal\Core\Form\FormBuilder::doBuildForm.
406 if (is_array($form_state_values['image_crop']) && isset($form_state_values['image_crop']['crop_wrapper'])) {
407 // Parse all values and get properties associate with the crop type.
408 foreach ($form_state_values['image_crop']['crop_wrapper'] as $crop_type_name => $properties) {
409 $properties = $properties['crop_container']['values'];
410 /** @var \Drupal\crop\Entity\CropType $crop_type */
411 $crop_type = $this->cropTypeStorage->load($crop_type_name);
413 // If the crop type needed is disabled or delete.
414 if (empty($crop_type) && $crop_type instanceof CropType) {
415 drupal_set_message(t("The CropType ('@cropType') is not active or not defined. Please verify configuration of image style or ImageWidgetCrop formatter configuration", ['@cropType' => $crop_type->id()]), 'error');
419 if (is_array($properties) && isset($properties)) {
420 $crop_exists = Crop::cropExists($entity->getFileUri(), $crop_type_name);
422 if ($properties['crop_applied'] == '1' && isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
423 $this->applyCrop($properties, $form_state_values['image_crop'], $crop_type);
427 // Get all imagesStyle used this crop_type.
428 $image_styles = $this->getImageStylesByCrop($crop_type_name);
429 $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $entity->getFileUri());
430 // If the entity already exist & is not deleted by user update
431 // $crop_type_name crop entity.
432 if ($properties['crop_applied'] == '0' && !empty($crops)) {
433 $this->deleteCrop($entity->getFileUri(), $crop_type, $entity->id());
435 elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
436 $this->updateCrop($properties, [
437 'file-uri' => $entity->getFileUri(),
438 'file-id' => $entity->id(),
447 drupal_set_message(t('No File element found.'), 'error');