d6ffd1798e6606ef8fd70bc4dbf747e45dc006db
[yaffs-website] / web / modules / contrib / image_widget_crop / src / ImageWidgetCropManager.php
1 <?php
2
3 namespace Drupal\image_widget_crop;
4
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;
13
14 /**
15  * ImageWidgetCropManager calculation class.
16  */
17 class ImageWidgetCropManager implements ImageWidgetCropInterface {
18
19   /**
20    * The entity type manager service.
21    *
22    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
23    */
24   protected $entityTypeManager;
25
26   /**
27    * The crop storage.
28    *
29    * @var \Drupal\Core\Entity\EntityStorageInterface
30    */
31   protected $cropStorage;
32
33   /**
34    * The crop storage.
35    *
36    * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
37    */
38   protected $cropTypeStorage;
39
40   /**
41    * The image style storage.
42    *
43    * @var \Drupal\Core\Entity\EntityStorageInterface
44    */
45   protected $imageStyleStorage;
46
47   /**
48    * The File storage.
49    *
50    * @var \Drupal\Core\Entity\EntityStorageInterface
51    */
52   protected $fileStorage;
53
54   /**
55    * Constructs a ImageWidgetCropManager object.
56    *
57    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
58    *   Entity type manager service.
59    */
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');
66   }
67
68   /**
69    * {@inheritdoc}
70    */
71   public function applyCrop(array $properties, $field_value, CropType $crop_type) {
72     $crop_properties = $this->getCropOriginalDimension($field_value, $properties);
73     if (!empty($crop_properties)) {
74       $this->saveCrop(
75         $crop_properties,
76         $field_value,
77         $crop_type,
78         FALSE
79       );
80     }
81   }
82
83   /**
84    * {@inheritdoc}
85    */
86   public function updateCrop(array $properties, $field_value, CropType $crop_type) {
87     $crop_properties = $this->getCropOriginalDimension($field_value, $properties);
88     if (!empty($crop_properties)) {
89       $image_styles = $this->getImageStylesByCrop($crop_type->id());
90
91       if (!empty($image_styles)) {
92         $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $field_value['file-uri']);
93       }
94
95       if (empty($crops)) {
96         $this->saveCrop($crop_properties, $field_value, $crop_type);
97         return;
98       }
99
100       /** @var \Drupal\crop\Entity\Crop $crop */
101       foreach ($crops as $crop) {
102         if (!$this->cropHasChanged($crop_properties, array_merge($crop->position(), $crop->size()))) {
103           return;
104         }
105
106         $this->updateCropProperties($crop, $crop_properties);
107         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()]));
108       }
109     }
110   }
111
112   /**
113    * {@inheritdoc}
114    */
115   public function saveCrop(array $crop_properties, $field_value, CropType $crop_type, $notify = TRUE) {
116     $values = [
117       'type' => $crop_type->id(),
118       'entity_id' => $field_value['file-id'],
119       'entity_type' => 'file',
120       'uri' => $field_value['file-uri'],
121       'x' => $crop_properties['x'],
122       'y' => $crop_properties['y'],
123       'width' => $crop_properties['width'],
124       'height' => $crop_properties['height'],
125     ];
126
127     /** @var \Drupal\crop\CropInterface $crop */
128     $crop = $this->cropStorage->create($values);
129     $crop->save();
130
131     if ($notify) {
132       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()]));
133     }
134   }
135
136   /**
137    * {@inheritdoc}
138    */
139   public function deleteCrop($file_uri, CropType $crop_type, $file_id) {
140     $image_styles = $this->getImageStylesByCrop($crop_type->id());
141     $crop = $this->cropStorage->loadByProperties([
142       'type' => $crop_type->id(),
143       'uri' => $file_uri,
144     ]);
145     $this->cropStorage->delete($crop);
146     $this->imageStylesOperations($image_styles, $file_uri);
147     drupal_set_message(t('The crop "@cropType" was successfully deleted for image "@filename".', [
148       '@cropType' => $crop_type->label(),
149       '@filename' => $this->fileStorage->load($file_id)->getFilename(),
150     ]));
151   }
152
153   /**
154    * {@inheritdoc}
155    */
156   public function getAxisCoordinates(array $axis, array $crop_selection) {
157     return [
158       'x' => (int) round($axis['x'] + ($crop_selection['width'] / 2)),
159       'y' => (int) round($axis['y'] + ($crop_selection['height'] / 2)),
160     ];
161   }
162
163   /**
164    * {@inheritdoc}
165    */
166   public function getCropOriginalDimension(array $field_values, array $properties) {
167     $crop_coordinates = [];
168
169     /** @var \Drupal\Core\Image\Image $image */
170     $image = \Drupal::service('image.factory')->get($field_values['file-uri']);
171     if (!$image->isValid()) {
172       drupal_set_message(t('The file "@file" is not valid, your crop is not applied.', [
173         '@file' => $field_values['file-uri'],
174       ]), 'error');
175       return $crop_coordinates;
176     }
177
178     // Get Center coordinate of crop zone on original image.
179     $axis_coordinate = $this->getAxisCoordinates(
180       ['x' => $properties['x'], 'y' => $properties['y']],
181       ['width' => $properties['width'], 'height' => $properties['height']]
182     );
183
184     // Calculate coordinates (position & sizes) of crop zone on original image.
185     $crop_coordinates['width'] = $properties['width'];
186     $crop_coordinates['height'] = $properties['height'];
187     $crop_coordinates['x'] = $axis_coordinate['x'];
188     $crop_coordinates['y'] = $axis_coordinate['y'];
189
190     return $crop_coordinates;
191   }
192
193   /**
194    * {@inheritdoc}
195    */
196   public function getEffectData(ImageStyle $image_style, $data_type) {
197     $data = NULL;
198     /* @var  \Drupal\image\ImageEffectInterface $effect */
199     foreach ($image_style->getEffects() as $uuid => $effect) {
200       $data_effect = $image_style->getEffect($uuid)->getConfiguration()['data'];
201       if (isset($data_effect[$data_type])) {
202         $data = $data_effect[$data_type];
203       }
204     }
205
206     return $data;
207   }
208
209   /**
210    * {@inheritdoc}
211    */
212   public function getImageStylesByCrop($crop_type_name) {
213     $styles = [];
214     $image_styles = $this->imageStyleStorage->loadMultiple();
215
216     /** @var \Drupal\image\Entity\ImageStyle $image_style */
217     foreach ($image_styles as $image_style) {
218       $image_style_data = $this->getEffectData($image_style, 'crop_type');
219       if (!empty($image_style_data) && ($image_style_data == $crop_type_name)) {
220         $styles[] = $image_style;
221       }
222     }
223
224     return $styles;
225   }
226
227   /**
228    * {@inheritdoc}
229    */
230   public function imageStylesOperations(array $image_styles, $file_uri, $create_derivative = FALSE) {
231     /** @var \Drupal\image\Entity\ImageStyle $image_style */
232     foreach ($image_styles as $image_style) {
233       if ($create_derivative) {
234         // Generate the image derivative uri.
235         $destination_uri = $image_style->buildUri($file_uri);
236
237         // Create a derivative of the original image with a good uri.
238         $image_style->createDerivative($file_uri, $destination_uri);
239       }
240       // Flush the cache of this ImageStyle.
241       $image_style->flush($file_uri);
242     }
243   }
244
245   /**
246    * {@inheritdoc}
247    */
248   public function updateCropProperties(Crop $crop, array $crop_properties) {
249     // Parse all properties if this crop have changed.
250     foreach ($crop_properties as $crop_coordinate => $value) {
251       // Edit the crop properties if he have changed.
252       $crop->set($crop_coordinate, $value, TRUE);
253     }
254
255     $crop->save();
256   }
257
258   /**
259    * {@inheritdoc}
260    */
261   public function loadImageStyleByCrop(array $image_styles, CropType $crop_type, $file_uri) {
262     $crops = [];
263     /** @var \Drupal\image\Entity\ImageStyle $image_style */
264     foreach ($image_styles as $image_style) {
265       /** @var \Drupal\crop\Entity\Crop $crop */
266       $crop = Crop::findCrop($file_uri, $crop_type->id());
267       if (!empty($crop)) {
268         $crops[$image_style->id()] = $crop;
269       }
270     }
271
272     return $crops;
273   }
274
275   /**
276    * {@inheritdoc}
277    */
278   public function cropHasChanged(array $crop_properties, array $old_crop) {
279     return !empty(array_diff_assoc($crop_properties, $old_crop));
280   }
281
282   /**
283    * {@inheritdoc}
284    */
285   public function getAvailableCropType(array $crop_list) {
286     $available_crop = [];
287     foreach ($crop_list as $crop_machine_name => $crop_label) {
288       $image_styles = $this->getImageStylesByCrop($crop_machine_name);
289       if (!empty($image_styles)) {
290         $available_crop[$crop_machine_name] = $crop_label;
291       }
292     }
293
294     return $available_crop;
295   }
296
297   /**
298    * {@inheritdoc}
299    */
300   public static function getCropProperties(Crop $crop) {
301     $anchor = $crop->anchor();
302     $size = $crop->size();
303     return [
304       'x' => $anchor['x'],
305       'y' => $anchor['y'],
306       'height' => $size['height'],
307       'width' => $size['width'],
308     ];
309   }
310
311   /**
312    * {@inheritdoc}
313    */
314   public function buildCropToEntity(EntityInterface $entity) {
315     if (isset($entity) && $entity instanceof FieldableEntityInterface) {
316       // Loop all fields of the saved entity.
317       foreach ($entity->getFields() as $entity_fields) {
318         // If current field is FileField and use imageWidgetCrop.
319         if ($entity_fields instanceof FileFieldItemList) {
320           /* First loop to get each elements independently in the field values.
321           Required if the image field cardinality > 1. */
322           foreach ($entity_fields->getValue() as $crop_elements) {
323             foreach ($crop_elements as $crop_element) {
324               if (is_array($crop_element) && isset($crop_element['crop_wrapper'])) {
325                 // Reload image since its URI could have been changed,
326                 // by other modules.
327                 /** @var \Drupal\file_entity\Entity\FileEntity $file */
328                 $file = $this->fileStorage->load($crop_element['file-id']);
329                 $crop_element['file-uri'] = $file->getFileUri();
330                 // Parse all value of a crop_wrapper element and get properties
331                 // associate with her CropType.
332                 foreach ($crop_element['crop_wrapper'] as $crop_type_name => $properties) {
333                   $properties = $properties['crop_container']['values'];
334                   /** @var \Drupal\crop\Entity\CropType $crop_type */
335                   $crop_type = $this->cropTypeStorage->load($crop_type_name);
336
337                   // If the crop type needed is disabled or delete.
338                   if (empty($crop_type) && $crop_type instanceof CropType) {
339                     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');
340                     return;
341                   }
342
343                   // If this crop is available to create an crop entity.
344                   if ($entity->isNew()) {
345                     if ($properties['crop_applied'] == '1' && isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
346                       $this->applyCrop($properties, $crop_element, $crop_type);
347                     }
348                   }
349                   else {
350                     // Get all imagesStyle used this crop_type.
351                     $image_styles = $this->getImageStylesByCrop($crop_type_name);
352                     $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $crop_element['file-uri']);
353                     // If the entity already exist & is not deleted by user
354                     // update $crop_type_name crop entity.
355                     if ($properties['crop_applied'] == '0' && !empty($crops)) {
356                       $this->deleteCrop($crop_element['file-uri'], $crop_type, $crop_element['file-id']);
357                     }
358                     elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
359                       $this->updateCrop($properties, $crop_element, $crop_type);
360                     }
361                   }
362                 }
363               }
364             }
365           }
366         }
367       }
368     }
369   }
370
371   /**
372    * {@inheritdoc}
373    */
374   public function buildCropToForm(FormStateInterface $form_state) {
375     if ($entity = $form_state->getFormObject()->getEntity()) {
376       $form_state_values = $form_state->getValues();
377       if (is_array($form_state_values['image_crop']) && isset($form_state_values['image_crop']['crop_wrapper'])) {
378         // Parse all values and get properties associate with the crop type.
379         foreach ($form_state_values['image_crop']['crop_wrapper'] as $crop_type_name => $properties) {
380           $properties = $properties['crop_container']['values'];
381           /** @var \Drupal\crop\Entity\CropType $crop_type */
382           $crop_type = $this->cropTypeStorage->load($crop_type_name);
383
384           // If the crop type needed is disabled or delete.
385           if (empty($crop_type) && $crop_type instanceof CropType) {
386             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');
387             return;
388           }
389
390           if (is_array($properties) && isset($properties)) {
391             $crop_exists = Crop::cropExists($entity->getFileUri(), $crop_type_name);
392             if (!$crop_exists) {
393               if ($properties['crop_applied'] == '1' && isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
394                 $this->applyCrop($properties, $form_state_values['image_crop'], $crop_type);
395               }
396             }
397             else {
398               // Get all imagesStyle used this crop_type.
399               $image_styles = $this->getImageStylesByCrop($crop_type_name);
400               $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $entity->getFileUri());
401               // If the entity already exist & is not deleted by user update
402               // $crop_type_name crop entity.
403               if ($properties['crop_applied'] == '0' && !empty($crops)) {
404                 $this->deleteCrop($entity->getFileUri(), $crop_type, $entity->id());
405               }
406               elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
407                 $this->updateCrop($properties, [
408                   'file-uri' => $entity->getFileUri(),
409                   'file-id' => $entity->id(),
410                 ], $crop_type);
411               }
412             }
413           }
414         }
415       }
416     }
417     else {
418       drupal_set_message(t('No File element found.'), 'error');
419       return;
420     }
421   }
422
423 }