5 * The Crop API Drupal module.
7 * Provides storage and API for image crops.
9 use Drupal\Component\Utility\UrlHelper;
10 use Drupal\Core\Form\FormStateInterface;
11 use Drupal\Core\StreamWrapper\PublicStream;
12 use Drupal\crop\Entity\Crop;
13 use Drupal\image\Entity\ImageStyle;
14 use Drupal\media\Entity\MediaType;
15 use Drupal\media\MediaTypeInterface;
16 use Drupal\media_entity\MediaBundleInterface;
17 use Drupal\file\FileInterface;
20 * Implements hook_theme().
22 function crop_theme() {
24 'crop_crop_summary' => [
25 'variables' => ['data' => [], 'effect' => []],
31 * Prepares variables for crop_crop summary template.
33 * Default template: crop-crop-summary.twig.html.
35 function template_preprocess_crop_crop_summary(&$variables) {
36 if (!empty($variables['data']['crop_type'])) {
37 $type = \Drupal::entityTypeManager()->getStorage('crop_type')->load($variables['data']['crop_type']);
38 $variables['data']['crop_type'] = $type->label();
43 * Implements hook_form_FORM_ID_alter().
45 * Adds crop configuration fields to media type form.
47 function crop_form_media_type_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
48 _crop_media_provider_form($form, $form_state);
52 * Implements hook_form_FORM_ID_alter().
54 * Adds crop configuration fields to media bundle form.
56 function crop_form_media_bundle_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
57 _crop_media_provider_form($form, $form_state);
61 * Helper function to avoid uneeded code duplication.
63 * @todo Delete this and media entity fallback when media is stable.
65 function _crop_media_provider_form(array &$form, FormStateInterface $form_state) {
66 /** @var \Drupal\Core\Config\Entity\ConfigEntityBundleBase $entity_type */
67 $entity_type = $form_state->getFormObject()->getEntity();
69 $allowed_field_types = ['file', 'image'];
71 /** @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields */
72 $fields = \Drupal::service('entity_field.manager')->getFieldDefinitions('media', $entity_type->id());
73 foreach ($fields as $field_name => $field) {
74 if (in_array($field->getType(), $allowed_field_types) && !$field->getFieldStorageDefinition()->isBaseField()) {
75 $options[$field_name] = $field->getLabel();
79 // Maintain compatibility with Media Entity.
80 if ($entity_type instanceof MediaType) {
81 $form['#entity_builders'][] = 'crop_media_type_form_builder';
84 $form['#entity_builders'][] = 'crop_media_bundle_form_builder';
88 '#type' => 'fieldset',
89 '#title' => t('Crop configuration'),
90 '#group' => 'source_dependent',
93 if (empty($options)) {
94 $form['crop']['image_field'] = [
99 $form['crop']['message'] = [
100 '#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.'),
106 $form['crop']['image_field'] = [
108 '#title' => t('Image field'),
109 '#default_value' => $entity_type->getThirdPartySetting('crop', 'image_field'),
110 '#options' => $options,
111 '#empty_option' => t('- Skip field -'),
112 '#empty_value' => '_none',
113 '#description' => t('Select field that stores image which needs to be cropped.'),
120 * Entity builder for Media bundle.
122 * Adds third party settings to Media bundle config entity.
124 * @see crop_form_media_bundle_form_alter()
126 function crop_media_bundle_form_builder($entity_type, MediaBundleInterface $bundle, &$form, FormStateInterface $form_state) {
127 $bundle->setThirdPartySetting('crop', 'image_field', $form_state->getValue('image_field'));
131 * Entity builder for Media type.
133 * Adds third party settings to Media type config entity.
135 * @see crop_form_media_type_edit_form_alter()
137 function crop_media_type_form_builder($entity_type, MediaTypeInterface $bundle, array &$form, FormStateInterface $form_state) {
138 $bundle->setThirdPartySetting('crop', 'image_field', $form_state->getValue('image_field'));
142 * Implements hook_ENTITY_TYPE_delete().
144 * Deletes orphaned crops when a file is deleted.
146 function crop_file_delete(FileInterface $file) {
147 // Get all crops for the file being deleted.
148 $crops = \Drupal::entityTypeManager()
150 ->loadByProperties(['uri' => $file->getFileUri()]);
152 foreach ($crops as $crop) {
158 * Implements hook_file_url_alter().
160 function crop_file_url_alter(&$uri) {
161 // Process only files that are stored in "styles" directory.
162 if (strpos($uri, '/styles/') !== FALSE && preg_match('/\/styles\/(.*?)\/(.*?)\/(.+)/', $uri, $match)) {
163 // Match image style, schema, file subdirectory and file name.
164 // Get the image style ID.
165 $image_style = $match[1];
166 // Get the file path without query parameter.
167 $parsed_uri = UrlHelper::parse($match[3]);
168 // Get the file URI using parsed schema and file path.
169 $file_uri = $match[2] . '://' . $parsed_uri['path'];
171 // Prevent double hashing, if there is a hash argument already, do not add
173 if (!empty($parsed_uri['query']['h'])) {
177 /** @var \Drupal\image\Entity\ImageStyle $image_style */
178 if (!$image_style = ImageStyle::load($image_style)) {
182 if ($crop = Crop::getCropFromImageStyle($file_uri, $image_style)) {
183 // Found a crop for this image, append a hash of it to the URL,
184 // so that browsers reload the image and CDNs and proxies can be bypassed.
185 $shortened_hash = substr(md5(implode($crop->position()) . implode($crop->anchor())), 0, 8);
187 // If the URI has a schema and that is not http, https or data, convert
188 // the URI to the external URL. Otherwise the appended query argument
190 // @see file_create_url()
191 $scheme = \Drupal::service('file_system')->uriScheme($uri);
192 if ($scheme && !in_array($scheme, ['http', 'https', 'data'])) {
193 if ($wrapper = \Drupal::service('stream_wrapper_manager')->getViaUri($uri)) {
194 $uri = $wrapper->getExternalUrl();
198 // Append either with a ? or a & if there are existing query arguments.
199 if (strpos($uri, '?') === FALSE) {
200 $uri .= '?h=' . $shortened_hash;
203 $uri .= '&h=' . $shortened_hash;