Version 1
[yaffs-website] / web / modules / contrib / image_widget_crop / src / ImageWidgetCropManager.php
diff --git a/web/modules/contrib/image_widget_crop/src/ImageWidgetCropManager.php b/web/modules/contrib/image_widget_crop/src/ImageWidgetCropManager.php
new file mode 100644 (file)
index 0000000..5f37ebe
--- /dev/null
@@ -0,0 +1,562 @@
+<?php
+
+namespace Drupal\image_widget_crop;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\crop\Entity\Crop;
+use Drupal\crop\Entity\CropType;
+use Drupal\file\Entity\File;
+use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
+use Drupal\image\Entity\ImageStyle;
+
+/**
+ * ImageWidgetCropManager calculation class.
+ */
+class ImageWidgetCropManager {
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface;
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The crop storage.
+   *
+   * @var \Drupal\crop\CropStorage.
+   */
+  protected $cropStorage;
+
+  /**
+   * The crop storage.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface.
+   */
+  protected $cropTypeStorage;
+
+  /**
+   * The image style storage.
+   *
+   * @var \Drupal\image\ImageStyleStorageInterface
+   */
+  protected $imageStyleStorage;
+
+  /**
+   * The File storage.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityStorage.
+   */
+  protected $fileStorage;
+
+  /**
+   * Constructs a ImageWidgetCropManager.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   Entity type manager service.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->cropStorage = $this->entityTypeManager->getStorage('crop');
+    $this->cropTypeStorage = $this->entityTypeManager->getStorage('crop_type');
+    $this->imageStyleStorage = $this->entityTypeManager->getStorage('image_style');
+    $this->fileStorage = $this->entityTypeManager->getStorage('file');
+  }
+
+  /**
+   * Create new crop entity with user properties.
+   *
+   * @param array $properties
+   *   All properties returned by the crop plugin (js),
+   *   and the size of thumbnail image.
+   * @param array|mixed $field_value
+   *   An array of values for the contained properties of image_crop widget.
+   * @param CropType $crop_type
+   *   The entity CropType.
+   */
+  public function applyCrop(array $properties, $field_value, CropType $crop_type) {
+    // Get Original sizes and position of crop zone.
+    $crop_properties = $this->getCropOriginalDimension($field_value, $properties);
+    // Get all imagesStyle used this crop_type.
+    $image_styles = $this->getImageStylesByCrop($crop_type->id());
+
+    $this->saveCrop($crop_properties, $field_value, $image_styles, $crop_type, FALSE);
+  }
+
+  /**
+   * Update old crop with new properties choose in UI.
+   *
+   * @param array $properties
+   *   All properties returned by the crop plugin (js),
+   *   and the size of thumbnail image.
+   * @param array|mixed $field_value
+   *   An array of values contain properties of image_crop widget.
+   * @param CropType $crop_type
+   *   The entity CropType.
+   */
+  public function updateCrop(array $properties, $field_value, CropType $crop_type) {
+    // Get Original sizes and position of crop zone.
+    $crop_properties = $this->getCropOriginalDimension($field_value, $properties);
+
+    // Get all imagesStyle used this crop_type.
+    $image_styles = $this->getImageStylesByCrop($crop_type->id());
+
+    if (!empty($image_styles)) {
+      $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $field_value['file-uri']);
+    }
+
+    // If any crop exist add new crop.
+    if (empty($crops)) {
+      $this->saveCrop($crop_properties, $field_value, $image_styles, $crop_type);
+      return;
+    }
+
+    foreach ($crops as $crop_element) {
+      // Get Only first crop entity @see https://www.drupal.org/node/2617818.
+      /** @var \Drupal\crop\Entity\Crop $crop */
+      $crop = $crop_element;
+
+      if (!$this->cropHasChanged($crop_properties, array_merge($crop->position(), $crop->size()))) {
+        return;
+      }
+
+      $this->updateCropProperties($crop, $crop_properties);
+      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()]));
+    }
+  }
+
+  /**
+   * Save the crop when this crop not exist.
+   *
+   * @param double[] $crop_properties
+   *   The properties of the crop applied to the original image (dimensions).
+   * @param array|mixed $field_value
+   *   An array of values for the contained properties of image_crop widget.
+   * @param array $image_styles
+   *   The list of imagesStyle available for this crop.
+   * @param CropType $crop_type
+   *   The entity CropType.
+   * @param bool $notify
+   *   Show notification after actions (default TRUE).
+   */
+  public function saveCrop(array $crop_properties, $field_value, array $image_styles, CropType $crop_type, $notify = TRUE) {
+    $values = [
+      'type' => $crop_type->id(),
+      'entity_id' => $field_value['file-id'],
+      'entity_type' => 'file',
+      'uri' => $field_value['file-uri'],
+      'x' => $crop_properties['x'],
+      'y' => $crop_properties['y'],
+      'width' => $crop_properties['width'],
+      'height' => $crop_properties['height'],
+    ];
+
+    // Save crop with previous values.
+    /** @var \Drupal\crop\CropInterface $crop */
+    $crop = $this->cropStorage->create($values);
+    $crop->save();
+
+    if ($notify) {
+      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()]));
+    }
+  }
+
+  /**
+   * Delete the crop when user delete it.
+   *
+   * @param string $file_uri
+   *   Uri of image uploaded by user.
+   * @param \Drupal\crop\Entity\CropType $crop_type
+   *   The CropType object.
+   * @param int $file_id
+   *   Id of image uploaded by user.
+   */
+  public function deleteCrop($file_uri, CropType $crop_type, $file_id) {
+    $image_styles = $this->getImageStylesByCrop($crop_type->id());
+    $crop = $this->cropStorage->loadByProperties([
+      'type' => $crop_type->id(),
+      'uri' => $file_uri,
+    ]);
+    $this->cropStorage->delete($crop);
+    $this->imageStylesOperations($image_styles, $file_uri);
+    drupal_set_message(t('The crop "@cropType" was successfully deleted for image "@filename".', [
+      '@cropType' => $crop_type->label(),
+      '@filename' => $this->fileStorage->load($file_id)->getFilename(),
+    ]));
+  }
+
+  /**
+   * Get center of crop selection.
+   *
+   * @param int[] $axis
+   *   Coordinates of x-axis & y-axis.
+   * @param array $crop_selection
+   *   Coordinates of crop selection (width & height).
+   *
+   * @return array<string,double>
+   *   Coordinates (x-axis & y-axis) of crop selection zone.
+   */
+  public function getAxisCoordinates(array $axis, array $crop_selection) {
+    return [
+      'x' => (int) round($axis['x'] + ($crop_selection['width'] / 2)),
+      'y' => (int) round($axis['y'] + ($crop_selection['height'] / 2)),
+    ];
+  }
+
+  /**
+   * Get the size and position of the crop.
+   *
+   * @param array $field_values
+   *   The original values of image.
+   * @param array $properties
+   *   The original height of image.
+   *
+   * @return null|array
+   *   The data dimensions (width & height) into this ImageStyle.
+   */
+  public function getCropOriginalDimension(array $field_values, array $properties) {
+    $crop_coordinates = [];
+
+    /** @var \Drupal\Core\Image\Image $image */
+    $image = \Drupal::service('image.factory')->get($field_values['file-uri']);
+    if (!$image->isValid()) {
+      drupal_set_message(t('The file "@file" is not valid, your crop is not applied.', [
+        '@file' => $field_values['file-uri'],
+      ]), 'error');
+      return NULL;
+    }
+
+    // Get Center coordinate of crop zone on original image.
+    $axis_coordinate = $this->getAxisCoordinates(
+      ['x' => $properties['x'], 'y' => $properties['y']],
+      ['width' => $properties['width'], 'height' => $properties['height']]
+    );
+
+    // Calculate coordinates (position & sizes) of crop zone on original image.
+    $crop_coordinates['width'] = $properties['width'];
+    $crop_coordinates['height'] = $properties['height'];
+    $crop_coordinates['x'] = $axis_coordinate['x'];
+    $crop_coordinates['y'] = $axis_coordinate['y'];
+
+    return $crop_coordinates;
+  }
+
+  /**
+   * Get one effect instead of ImageStyle.
+   *
+   * @param \Drupal\image\Entity\ImageStyle $image_style
+   *   The ImageStyle to get data.
+   * @param string $data_type
+   *   The type of data needed in current ImageStyle.
+   *
+   * @return mixed|null
+   *   The effect data in current ImageStyle.
+   */
+  public function getEffectData(ImageStyle $image_style, $data_type) {
+    $data = NULL;
+    /* @var  \Drupal\image\ImageEffectInterface $effect */
+    foreach ($image_style->getEffects() as $uuid => $effect) {
+      $data_effect = $image_style->getEffect($uuid)->getConfiguration()['data'];
+      if (isset($data_effect[$data_type])) {
+        $data = $data_effect[$data_type];
+      }
+    }
+
+    return $data;
+  }
+
+  /**
+   * Get the imageStyle using this crop_type.
+   *
+   * @param string $crop_type_name
+   *   The id of the current crop_type entity.
+   *
+   * @return array
+   *   All imageStyle used by this crop_type.
+   */
+  public function getImageStylesByCrop($crop_type_name) {
+    $styles = [];
+    $image_styles = $this->imageStyleStorage->loadMultiple();
+
+    /** @var \Drupal\image\Entity\ImageStyle $image_style */
+    foreach ($image_styles as $image_style) {
+      $image_style_data = $this->getEffectData($image_style, 'crop_type');
+      if (!empty($image_style_data) && ($image_style_data == $crop_type_name)) {
+        $styles[] = $image_style;
+      }
+    }
+
+    return $styles;
+  }
+
+  /**
+   * Apply different operation on ImageStyles.
+   *
+   * @param array $image_styles
+   *   All ImageStyles used by this cropType.
+   * @param string $file_uri
+   *   Uri of image uploaded by user.
+   * @param bool $create_derivative
+   *   Boolean to create an derivative of the image uploaded.
+   */
+  public function imageStylesOperations(array $image_styles, $file_uri, $create_derivative = FALSE) {
+    /** @var \Drupal\image\Entity\ImageStyle $image_style */
+    foreach ($image_styles as $image_style) {
+      if ($create_derivative) {
+        // Generate the image derivative uri.
+        $destination_uri = $image_style->buildUri($file_uri);
+
+        // Create a derivative of the original image with a good uri.
+        $image_style->createDerivative($file_uri, $destination_uri);
+      }
+      // Flush the cache of this ImageStyle.
+      $image_style->flush($file_uri);
+    }
+  }
+
+  /**
+   * Update existent crop entity properties.
+   *
+   * @param \Drupal\crop\Entity\Crop $crop
+   *   The crop object loaded.
+   * @param array $crop_properties
+   *   The machine name of ImageStyle.
+   */
+  public function updateCropProperties(Crop $crop, array $crop_properties) {
+    // Parse all properties if this crop have changed.
+    foreach ($crop_properties as $crop_coordinate => $value) {
+      // Edit the crop properties if he have changed.
+      $crop->set($crop_coordinate, $value, TRUE);
+    }
+
+    $crop->save();
+  }
+
+  /**
+   * Load all crop using the ImageStyles.
+   *
+   * @param array $image_styles
+   *   All ImageStyle for this current CROP.
+   * @param CropType $crop_type
+   *   The entity CropType.
+   * @param string $file_uri
+   *   Uri of uploded file.
+   *
+   * @return array
+   *   All crop used this ImageStyle.
+   */
+  public function loadImageStyleByCrop(array $image_styles, CropType $crop_type, $file_uri) {
+    $crops = [];
+    /** @var \Drupal\image\Entity\ImageStyle $image_style */
+    foreach ($image_styles as $image_style) {
+      /** @var \Drupal\crop\Entity\Crop $crop */
+      $crop = Crop::findCrop($file_uri, $crop_type->id());
+      if (!empty($crop)) {
+        $crops[$image_style->id()] = $crop;
+      }
+    }
+
+    return $crops;
+  }
+
+  /**
+   * Compare crop zone properties when user saved one crop.
+   *
+   * @param array $crop_properties
+   *   The crop properties after saved the form.
+   * @param array $old_crop
+   *   The crop properties save in this crop entity,
+   *   Only if this crop already exist.
+   *
+   * @return bool
+   *   Return true if properties is not identical.
+   */
+  public function cropHasChanged(array $crop_properties, array $old_crop) {
+    if (!empty(array_diff_assoc($crop_properties, $old_crop))) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Verify if ImageStyle is correctly configured.
+   *
+   * @param array $styles
+   *   The list of available ImageStyle.
+   *
+   * @return array<integer>
+   *   The list of styles filtred.
+   */
+  public function getAvailableCropImageStyle(array $styles) {
+    $available_styles = [];
+    foreach ($styles as $style_id => $style_label) {
+      $style_loaded = $this->imageStyleStorage->loadByProperties(['name' => $style_id]);
+      /** @var \Drupal\image\Entity\ImageStyle $image_style */
+      $image_style = $style_loaded[$style_id];
+      $effect_data = $this->getEffectData($image_style, 'width');
+      if (!empty($effect_data)) {
+        $available_styles[$style_id] = $style_label;
+      }
+    }
+
+    return $available_styles;
+  }
+
+  /**
+   * Verify if the crop is used by a ImageStyle.
+   *
+   * @param array $crop_list
+   *   The list of existent Crop Type.
+   *
+   * @return array<integer>
+   *   The list of Crop Type filtred.
+   */
+  public function getAvailableCropType(array $crop_list) {
+    $available_crop = [];
+    foreach ($crop_list as $crop_machine_name => $crop_label) {
+      $image_styles = $this->getImageStylesByCrop($crop_machine_name);
+      if (!empty($image_styles)) {
+        $available_crop[$crop_machine_name] = $crop_label;
+      }
+    }
+
+    return $available_crop;
+  }
+
+  /**
+   * Get All sizes properties of the crops for an file.
+   *
+   * @param \Drupal\crop\Entity\Crop $crop
+   *   All crops attached to this file based on URI.
+   *
+   * @return array<array>
+   *   Get all crop zone properties (x, y, height, width),
+   */
+  public static function getCropProperties(Crop $crop) {
+    $anchor = $crop->anchor();
+    $size = $crop->size();
+    return [
+      'x' => $anchor['x'],
+      'y' => $anchor['y'],
+      'height' => $size['height'],
+      'width' => $size['width'],
+    ];
+  }
+
+  /**
+   * Fetch all fields FileField and use "image_crop" element on an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity object.
+   */
+  public function buildCropToEntity(EntityInterface $entity) {
+    if (isset($entity) && $entity instanceof FieldableEntityInterface) {
+      // Loop all fields of the saved entity.
+      foreach ($entity->getFields() as $entity_fields) {
+        // If current field is FileField and use imageWidgetCrop.
+        if ($entity_fields instanceof FileFieldItemList) {
+          /* First loop to get each elements independently in the field values.
+          Required if the image field cardinality > 1. */
+          foreach ($entity_fields->getValue() as $crop_elements) {
+            foreach ($crop_elements as $crop_element) {
+              if (is_array($crop_element) && isset($crop_element['crop_wrapper'])) {
+                // Reload image since its URI could have been changed by other modules.
+                /** @var \Drupal\file_entity\Entity\FileEntity $file */
+                $file = $this->fileStorage->load($crop_element['file-id']);
+                $crop_element['file-uri'] = $file->getFileUri();
+                // Parse all value of a crop_wrapper element and get properties
+                // associate with her CropType.
+                foreach ($crop_element['crop_wrapper'] as $crop_type_name => $properties) {
+                  $properties = $properties['crop_container']['values'];
+                  /** @var \Drupal\crop\Entity\CropType $crop_type */
+                  $crop_type = $this->cropTypeStorage->load($crop_type_name);
+
+                  // If the crop type needed is disabled or delete.
+                  if (empty($crop_type) && $crop_type instanceof CropType) {
+                    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');
+                    return;
+                  }
+
+                  // If this crop is available to create an crop entity.
+                  if ($entity->isNew()) {
+                    if ($properties['crop_applied'] == '1' && isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
+                      $this->applyCrop($properties, $crop_element, $crop_type);
+                    }
+                  }
+                  else {
+                    // Get all imagesStyle used this crop_type.
+                    $image_styles = $this->getImageStylesByCrop($crop_type_name);
+                    $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $crop_element['file-uri']);
+                    // If the entity already exist & is not deleted by user
+                    // update $crop_type_name crop entity.
+                    if ($properties['crop_applied'] == '0' && !empty($crops)) {
+                      $this->deleteCrop($crop_element['file-uri'], $crop_type, $crop_element['file-id']);
+                    }
+                    elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
+                      $this->updateCrop($properties, $crop_element, $crop_type);
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Fetch all form elements using image_crop element.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function buildCropToForm(FormStateInterface $form_state) {
+    /** @var \Drupal\file_entity\Entity\FileEntity $entity */
+    $entity = $form_state->getFormObject()->getEntity();
+    $form_state_values = $form_state->getValues();
+    if (is_array($form_state_values['image_crop']) && isset($form_state_values['image_crop']['crop_wrapper'])) {
+      // Parse all values and get properties associate with the crop type.
+      foreach ($form_state_values['image_crop']['crop_wrapper'] as $crop_type_name => $properties) {
+        $properties = $properties['crop_container']['values'];
+        /** @var \Drupal\crop\Entity\CropType $crop_type */
+        $crop_type = $this->cropTypeStorage->load($crop_type_name);
+
+        // If the crop type needed is disabled or delete.
+        if (empty($crop_type) && $crop_type instanceof CropType) {
+          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');
+          return;
+        }
+
+        if (is_array($properties) && isset($properties)) {
+          $crop_exists = Crop::cropExists($entity->getFileUri(), $crop_type_name);
+          if (!$crop_exists) {
+            if ($properties['crop_applied'] == '1' && isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
+              $this->applyCrop($properties, $form_state_values['image_crop'], $crop_type);
+            }
+          }
+          else {
+            // Get all imagesStyle used this crop_type.
+            $image_styles = $this->getImageStylesByCrop($crop_type_name);
+            $crops = $this->loadImageStyleByCrop($image_styles, $crop_type, $entity->getFileUri());
+            // If the entity already exist & is not deleted by user update
+            // $crop_type_name crop entity.
+            if ($properties['crop_applied'] == '0' && !empty($crops)) {
+              $this->deleteCrop($entity->getFileUri(), $crop_type, $entity->id());
+            }
+            elseif (isset($properties) && (!empty($properties['width']) && !empty($properties['height']))) {
+              $this->updateCrop($properties, [
+                'file-uri' => $entity->getFileUri(),
+                'file-id' => $entity->id(),
+              ], $crop_type);
+            }
+          }
+        }
+      }
+    }
+  }
+
+}