b72489522fffa8cafc85dbb76a89097c3c5af282
[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     // 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());
76
77     $this->saveCrop(
78       $crop_properties,
79       $field_value,
80       $image_styles,
81       $crop_type,
82       FALSE
83     );
84   }
85
86   /**
87    * {@inheritdoc}
88    */
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);
92
93     // Get all imagesStyle used this crop_type.
94     $image_styles = $this->getImageStylesByCrop($crop_type->id());
95
96     if (!empty($image_styles)) {
97       $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $field_value['file-uri']);
98     }
99
100     // If any crop exist add new crop.
101     if (empty($crops)) {
102       $this->saveCrop($crop_properties, $field_value, $image_styles, $crop_type);
103       return;
104     }
105
106     /** @var \Drupal\crop\Entity\Crop $crop */
107     foreach ($crops as $crop) {
108       if (!$this->cropHasChanged($crop_properties, array_merge($crop->position(), $crop->size()))) {
109         return;
110       }
111
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()]));
114     }
115   }
116
117   /**
118    * {@inheritdoc}
119    */
120   public function saveCrop(array $crop_properties, $field_value, array $image_styles, CropType $crop_type, $notify = TRUE) {
121     $values = [
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'],
130     ];
131
132     // Save crop with previous values.
133     /** @var \Drupal\crop\CropInterface $crop */
134     $crop = $this->cropStorage->create($values);
135     $crop->save();
136
137     if ($notify) {
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()]));
139     }
140   }
141
142   /**
143    * {@inheritdoc}
144    */
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(),
149       'uri' => $file_uri,
150     ]);
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(),
156     ]));
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public function getAxisCoordinates(array $axis, array $crop_selection) {
163     return [
164       'x' => (int) round($axis['x'] + ($crop_selection['width'] / 2)),
165       'y' => (int) round($axis['y'] + ($crop_selection['height'] / 2)),
166     ];
167   }
168
169   /**
170    * {@inheritdoc}
171    */
172   public function getCropOriginalDimension(array $field_values, array $properties) {
173     $crop_coordinates = [];
174
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'],
180       ]), 'error');
181       return NULL;
182     }
183
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']]
188     );
189
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'];
195
196     return $crop_coordinates;
197   }
198
199   /**
200    * {@inheritdoc}
201    */
202   public function getEffectData(ImageStyle $image_style, $data_type) {
203     $data = NULL;
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];
209       }
210     }
211
212     return $data;
213   }
214
215   /**
216    * {@inheritdoc}
217    */
218   public function getImageStylesByCrop($crop_type_name) {
219     $styles = [];
220     $image_styles = $this->imageStyleStorage->loadMultiple();
221
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;
227       }
228     }
229
230     return $styles;
231   }
232
233   /**
234    * {@inheritdoc}
235    */
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);
242
243         // Create a derivative of the original image with a good uri.
244         $image_style->createDerivative($file_uri, $destination_uri);
245       }
246       // Flush the cache of this ImageStyle.
247       $image_style->flush($file_uri);
248     }
249   }
250
251   /**
252    * {@inheritdoc}
253    */
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);
259     }
260
261     $crop->save();
262   }
263
264   /**
265    * {@inheritdoc}
266    */
267   public function loadImageStyleByCrop(array $image_styles, CropType $crop_type, $file_uri) {
268     $crops = [];
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());
273       if (!empty($crop)) {
274         $crops[$image_style->id()] = $crop;
275       }
276     }
277
278     return $crops;
279   }
280
281   /**
282    * {@inheritdoc}
283    */
284   public function cropHasChanged(array $crop_properties, array $old_crop) {
285     if (!empty(array_diff_assoc($crop_properties, $old_crop))) {
286       return TRUE;
287     }
288     return FALSE;
289   }
290
291   /**
292    * {@inheritdoc}
293    */
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;
303       }
304     }
305
306     return $available_styles;
307   }
308
309   /**
310    * {@inheritdoc}
311    */
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;
318       }
319     }
320
321     return $available_crop;
322   }
323
324   /**
325    * {@inheritdoc}
326    */
327   public static function getCropProperties(Crop $crop) {
328     $anchor = $crop->anchor();
329     $size = $crop->size();
330     return [
331       'x' => $anchor['x'],
332       'y' => $anchor['y'],
333       'height' => $size['height'],
334       'width' => $size['width'],
335     ];
336   }
337
338   /**
339    * {@inheritdoc}
340    */
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,
353                 // by other modules.
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);
363
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');
367                     return;
368                   }
369
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);
374                     }
375                   }
376                   else {
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']);
384                     }
385                     elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
386                       $this->updateCrop($properties, $crop_element, $crop_type);
387                     }
388                   }
389                 }
390               }
391             }
392           }
393         }
394       }
395     }
396   }
397
398   /**
399    * {@inheritdoc}
400    */
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);
412
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');
416             return;
417           }
418
419           if (is_array($properties) && isset($properties)) {
420             $crop_exists = Crop::cropExists($entity->getFileUri(), $crop_type_name);
421             if (!$crop_exists) {
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);
424               }
425             }
426             else {
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());
434               }
435               elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
436                 $this->updateCrop($properties, [
437                   'file-uri' => $entity->getFileUri(),
438                   'file-id' => $entity->id(),
439                 ], $crop_type);
440               }
441             }
442           }
443         }
444       }
445     }
446     else {
447       drupal_set_message(t('No File element found.'), 'error');
448       return;
449     }
450   }
451
452 }