You are here

crop.module in Crop API 8

Same filename and directory in other branches
  1. 8.2 crop.module

The Crop API Drupal module.

Provides storage and API for image crops.

File

crop.module
View source
<?php

/**
 * @file
 * The Crop API Drupal module.
 *
 * Provides storage and API for image crops.
 */
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Form\FormStateInterface;
use Drupal\crop\Entity\Crop;
use Drupal\image\Entity\ImageStyle;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaTypeInterface;
use Drupal\media_entity\MediaBundleInterface;
use Drupal\file\FileInterface;
use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_theme().
 */
function crop_theme() {
  return [
    'crop_crop_summary' => [
      'variables' => [
        'data' => [],
        'effect' => [],
      ],
    ],
  ];
}

/**
 * Implements hook_help().
 */
function crop_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.crop':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t("<a href=':crop'>Crop</a> provides basic API for image cropping. This module won't do much by itself. Users should pick one of UI modules that utilize this API.</a>", [
        ':crop' => 'http://drupal.org/project/crop',
      ]) . '</p>';
      $output .= '<h3>' . t('Configuration') . '</h3>';
      $output .= '<p>' . t('This is API module. In order to crop your images you need a UI module. There are currently two UI modules that use Crop API:') . '</p>';
      $output .= '<ul>';
      $output .= '<li>' . t('<a href=":image_widget_crop">Image widget crop</a>', [
        ':image_widget_crop' => 'https://www.drupal.org/project/image_widget_crop',
      ]) . '</li>';
      $output .= '<li>' . t('<a href=":focal_point">Focal point</a>', [
        ':focal_point' => 'https://www.drupal.org/project/focal_point',
      ]) . '</li>';
      $output .= '</ul>';
      $output .= '<h3>' . t('Technical details') . '</h3>';
      $output .= '<p>' . t('Initial discussion can be found on <a href=":manual">manual crop issue queue</a>.', [
        ':manual' => 'https://www.drupal.org/node/2368945',
      ]) . '</p>';
      return $output;
  }
}

/**
 * Prepares variables for crop_crop summary template.
 *
 * Default template: crop-crop-summary.twig.html.
 */
function template_preprocess_crop_crop_summary(&$variables) {
  if (!empty($variables['data']['crop_type'])) {
    $type = \Drupal::entityTypeManager()
      ->getStorage('crop_type')
      ->load($variables['data']['crop_type']);
    $variables['data']['crop_type'] = $type
      ->label();
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds crop configuration fields to media type form.
 */
function crop_form_media_type_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  _crop_media_provider_form($form, $form_state);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds crop configuration fields to media bundle form.
 */
function crop_form_media_bundle_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  _crop_media_provider_form($form, $form_state);
}

/**
 * Helper function to avoid uneeded code duplication.
 *
 * @todo Delete this and media entity fallback when media is stable.
 */
function _crop_media_provider_form(array &$form, FormStateInterface $form_state) {

  /** @var \Drupal\Core\Config\Entity\ConfigEntityBundleBase $entity_type */
  $entity_type = $form_state
    ->getFormObject()
    ->getEntity();
  $options = [];
  $allowed_field_types = [
    'file',
    'image',
  ];

  /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields */
  $fields = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions('media', $entity_type
    ->id());
  foreach ($fields as $field_name => $field) {
    if (in_array($field
      ->getType(), $allowed_field_types) && !$field
      ->getFieldStorageDefinition()
      ->isBaseField()) {
      $options[$field_name] = $field
        ->getLabel();
    }
  }

  // Maintain compatibility with Media Entity.
  if ($entity_type instanceof MediaType) {
    $form['#entity_builders'][] = 'crop_media_type_form_builder';
  }
  else {
    $form['#entity_builders'][] = 'crop_media_bundle_form_builder';
  }
  $form['crop'] = [
    '#type' => 'fieldset',
    '#title' => t('Crop configuration'),
    '#group' => 'source_dependent',
  ];
  if (empty($options)) {
    $form['crop']['image_field'] = [
      '#type' => 'value',
      '#value' => NULL,
    ];
    $form['crop']['message'] = [
      '#markup' => t('There are no file or image fields on this bundle at the moment. In order to configure crop add at least one such field and come back.'),
    ];
    return;
  }
  $form['crop']['image_field'] = [
    '#type' => 'select',
    '#title' => t('Image field'),
    '#default_value' => $entity_type
      ->getThirdPartySetting('crop', 'image_field'),
    '#options' => $options,
    '#empty_option' => t('- Skip field -'),
    '#empty_value' => '_none',
    '#description' => t('Select field that stores image which needs to be cropped.'),
  ];
  return $form;
}

/**
 * Entity builder for Media bundle.
 *
 * Adds third party settings to Media bundle config entity.
 *
 * @see crop_form_media_bundle_form_alter()
 */
function crop_media_bundle_form_builder($entity_type, MediaBundleInterface $bundle, &$form, FormStateInterface $form_state) {
  $bundle
    ->setThirdPartySetting('crop', 'image_field', $form_state
    ->getValue('image_field'));
}

/**
 * Entity builder for Media type.
 *
 * Adds third party settings to Media type config entity.
 *
 * @see crop_form_media_type_edit_form_alter()
 */
function crop_media_type_form_builder($entity_type, MediaTypeInterface $bundle, array &$form, FormStateInterface $form_state) {
  $bundle
    ->setThirdPartySetting('crop', 'image_field', $form_state
    ->getValue('image_field'));
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 *
 * Deletes orphaned crops when a file is deleted.
 */
function crop_file_delete(FileInterface $file) {

  // Get all crops for the file being deleted.
  $crops = \Drupal::entityTypeManager()
    ->getStorage('crop')
    ->loadByProperties([
    'uri' => $file
      ->getFileUri(),
  ]);
  foreach ($crops as $crop) {
    $crop
      ->delete();
  }
}

/**
 * Implements hook_file_url_alter().
 */
function crop_file_url_alter(&$uri) {

  // Process only files that are stored in "styles" directory.
  if (strpos($uri, '/styles/') !== FALSE && preg_match('/\\/styles\\/(.*?)\\/(.*?)\\/(.+)/', $uri, $match)) {

    // Match image style, schema, file subdirectory and file name.
    // Get the image style ID.
    $image_style = $match[1];

    // Get the file path without query parameter.
    $parsed_uri = UrlHelper::parse($match[3]);

    // Get the file URI using parsed schema and file path.
    $file_uri = $match[2] . '://' . $parsed_uri['path'];

    // Prevent double hashing, if there is a hash argument already, do not add
    // it again.
    if (!empty($parsed_uri['query']['h'])) {
      return;
    }

    /** @var \Drupal\image\Entity\ImageStyle $image_style */
    if (!($image_style = ImageStyle::load($image_style))) {
      return;
    }
    if ($crop = Crop::getCropFromImageStyle($file_uri, $image_style)) {

      // Found a crop for this image, append a hash of it to the URL,
      // so that browsers reload the image and CDNs and proxies can be bypassed.
      $shortened_hash = substr(md5(implode($crop
        ->position()) . implode($crop
        ->anchor())), 0, 8);

      // If the URI has a schema and that is not http, https or data, convert
      // the URI to the external URL. Otherwise the appended query argument
      // will be encoded.
      // @see file_create_url()
      $scheme = \Drupal::service('file_system')
        ->uriScheme($uri);
      if ($scheme && !in_array($scheme, [
        'http',
        'https',
        'data',
      ])) {
        if ($wrapper = \Drupal::service('stream_wrapper_manager')
          ->getViaUri($uri)) {
          $uri = $wrapper
            ->getExternalUrl();
        }
      }

      // Append either with a ? or a & if there are existing query arguments.
      if (strpos($uri, '?') === FALSE) {
        $uri .= '?h=' . $shortened_hash;
      }
      else {
        $uri .= '&h=' . $shortened_hash;
      }
    }
  }
}

Functions

Namesort descending Description
crop_file_delete Implements hook_ENTITY_TYPE_delete().
crop_file_url_alter Implements hook_file_url_alter().
crop_form_media_bundle_edit_form_alter Implements hook_form_FORM_ID_alter().
crop_form_media_type_edit_form_alter Implements hook_form_FORM_ID_alter().
crop_help Implements hook_help().
crop_media_bundle_form_builder Entity builder for Media bundle.
crop_media_type_form_builder Entity builder for Media type.
crop_theme Implements hook_theme().
template_preprocess_crop_crop_summary Prepares variables for crop_crop summary template.
_crop_media_provider_form Helper function to avoid uneeded code duplication.