Version 1
[yaffs-website] / web / core / modules / image / src / Plugin / Field / FieldWidget / ImageWidget.php
1 <?php
2
3 namespace Drupal\image\Plugin\Field\FieldWidget;
4
5 use Drupal\Core\Field\FieldItemListInterface;
6 use Drupal\Component\Utility\NestedArray;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\file\Entity\File;
9 use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
10 use Drupal\image\Entity\ImageStyle;
11
12 /**
13  * Plugin implementation of the 'image_image' widget.
14  *
15  * @FieldWidget(
16  *   id = "image_image",
17  *   label = @Translation("Image"),
18  *   field_types = {
19  *     "image"
20  *   }
21  * )
22  */
23 class ImageWidget extends FileWidget {
24
25   /**
26    * {@inheritdoc}
27    */
28   public static function defaultSettings() {
29     return [
30       'progress_indicator' => 'throbber',
31       'preview_image_style' => 'thumbnail',
32     ] + parent::defaultSettings();
33   }
34
35   /**
36    * {@inheritdoc}
37    */
38   public function settingsForm(array $form, FormStateInterface $form_state) {
39     $element = parent::settingsForm($form, $form_state);
40
41     $element['preview_image_style'] = [
42       '#title' => t('Preview image style'),
43       '#type' => 'select',
44       '#options' => image_style_options(FALSE),
45       '#empty_option' => '<' . t('no preview') . '>',
46       '#default_value' => $this->getSetting('preview_image_style'),
47       '#description' => t('The preview image will be shown while editing the content.'),
48       '#weight' => 15,
49     ];
50
51     return $element;
52   }
53
54   /**
55    * {@inheritdoc}
56    */
57   public function settingsSummary() {
58     $summary = parent::settingsSummary();
59
60     $image_styles = image_style_options(FALSE);
61     // Unset possible 'No defined styles' option.
62     unset($image_styles['']);
63     // Styles could be lost because of enabled/disabled modules that defines
64     // their styles in code.
65     $image_style_setting = $this->getSetting('preview_image_style');
66     if (isset($image_styles[$image_style_setting])) {
67       $preview_image_style = t('Preview image style: @style', ['@style' => $image_styles[$image_style_setting]]);
68     }
69     else {
70       $preview_image_style = t('No preview');
71     }
72
73     array_unshift($summary, $preview_image_style);
74
75     return $summary;
76   }
77
78   /**
79    * Overrides \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formMultipleElements().
80    *
81    * Special handling for draggable multiple widgets and 'add more' button.
82    */
83   protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
84     $elements = parent::formMultipleElements($items, $form, $form_state);
85
86     $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
87     $file_upload_help = [
88       '#theme' => 'file_upload_help',
89       '#description' => '',
90       '#upload_validators' => $elements[0]['#upload_validators'],
91       '#cardinality' => $cardinality,
92     ];
93     if ($cardinality == 1) {
94       // If there's only one field, return it as delta 0.
95       if (empty($elements[0]['#default_value']['fids'])) {
96         $file_upload_help['#description'] = $this->getFilteredDescription();
97         $elements[0]['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
98       }
99     }
100     else {
101       $elements['#file_upload_description'] = $file_upload_help;
102     }
103
104     return $elements;
105   }
106
107   /**
108    * {@inheritdoc}
109    */
110   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
111     $element = parent::formElement($items, $delta, $element, $form, $form_state);
112
113     $field_settings = $this->getFieldSettings();
114
115     // Add upload resolution validation.
116     if ($field_settings['max_resolution'] || $field_settings['min_resolution']) {
117       $element['#upload_validators']['file_validate_image_resolution'] = [$field_settings['max_resolution'], $field_settings['min_resolution']];
118     }
119
120     // If not using custom extension validation, ensure this is an image.
121     $supported_extensions = ['png', 'gif', 'jpg', 'jpeg'];
122     $extensions = isset($element['#upload_validators']['file_validate_extensions'][0]) ? $element['#upload_validators']['file_validate_extensions'][0] : implode(' ', $supported_extensions);
123     $extensions = array_intersect(explode(' ', $extensions), $supported_extensions);
124     $element['#upload_validators']['file_validate_extensions'][0] = implode(' ', $extensions);
125
126     // Add mobile device image capture acceptance.
127     $element['#accept'] = 'image/*';
128
129     // Add properties needed by process() method.
130     $element['#preview_image_style'] = $this->getSetting('preview_image_style');
131     $element['#title_field'] = $field_settings['title_field'];
132     $element['#title_field_required'] = $field_settings['title_field_required'];
133     $element['#alt_field'] = $field_settings['alt_field'];
134     $element['#alt_field_required'] = $field_settings['alt_field_required'];
135
136     // Default image.
137     $default_image = $field_settings['default_image'];
138     if (empty($default_image['uuid'])) {
139       $default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
140     }
141     // Convert the stored UUID into a file ID.
142     if (!empty($default_image['uuid']) && $entity = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid'])) {
143       $default_image['fid'] = $entity->id();
144     }
145     $element['#default_image'] = !empty($default_image['fid']) ? $default_image : [];
146
147     return $element;
148   }
149
150   /**
151    * Form API callback: Processes a image_image field element.
152    *
153    * Expands the image_image type to include the alt and title fields.
154    *
155    * This method is assigned as a #process callback in formElement() method.
156    */
157   public static function process($element, FormStateInterface $form_state, $form) {
158     $item = $element['#value'];
159     $item['fids'] = $element['fids']['#value'];
160
161     $element['#theme'] = 'image_widget';
162
163     // Add the image preview.
164     if (!empty($element['#files']) && $element['#preview_image_style']) {
165       $file = reset($element['#files']);
166       $variables = [
167         'style_name' => $element['#preview_image_style'],
168         'uri' => $file->getFileUri(),
169       ];
170
171       // Determine image dimensions.
172       if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
173         $variables['width'] = $element['#value']['width'];
174         $variables['height'] = $element['#value']['height'];
175       }
176       else {
177         $image = \Drupal::service('image.factory')->get($file->getFileUri());
178         if ($image->isValid()) {
179           $variables['width'] = $image->getWidth();
180           $variables['height'] = $image->getHeight();
181         }
182         else {
183           $variables['width'] = $variables['height'] = NULL;
184         }
185       }
186
187       $element['preview'] = [
188         '#weight' => -10,
189         '#theme' => 'image_style',
190         '#width' => $variables['width'],
191         '#height' => $variables['height'],
192         '#style_name' => $variables['style_name'],
193         '#uri' => $variables['uri'],
194       ];
195
196       // Store the dimensions in the form so the file doesn't have to be
197       // accessed again. This is important for remote files.
198       $element['width'] = [
199         '#type' => 'hidden',
200         '#value' => $variables['width'],
201       ];
202       $element['height'] = [
203         '#type' => 'hidden',
204         '#value' => $variables['height'],
205       ];
206     }
207     elseif (!empty($element['#default_image'])) {
208       $default_image = $element['#default_image'];
209       $file = File::load($default_image['fid']);
210       if (!empty($file)) {
211         $element['preview'] = [
212           '#weight' => -10,
213           '#theme' => 'image_style',
214           '#width' => $default_image['width'],
215           '#height' => $default_image['height'],
216           '#style_name' => $element['#preview_image_style'],
217           '#uri' => $file->getFileUri(),
218         ];
219       }
220     }
221
222     // Add the additional alt and title fields.
223     $element['alt'] = [
224       '#title' => t('Alternative text'),
225       '#type' => 'textfield',
226       '#default_value' => isset($item['alt']) ? $item['alt'] : '',
227       '#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
228       // @see https://www.drupal.org/node/465106#alt-text
229       '#maxlength' => 512,
230       '#weight' => -12,
231       '#access' => (bool) $item['fids'] && $element['#alt_field'],
232       '#required' => $element['#alt_field_required'],
233       '#element_validate' => $element['#alt_field_required'] == 1 ? [[get_called_class(), 'validateRequiredFields']] : [],
234     ];
235     $element['title'] = [
236       '#type' => 'textfield',
237       '#title' => t('Title'),
238       '#default_value' => isset($item['title']) ? $item['title'] : '',
239       '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
240       '#maxlength' => 1024,
241       '#weight' => -11,
242       '#access' => (bool) $item['fids'] && $element['#title_field'],
243       '#required' => $element['#title_field_required'],
244       '#element_validate' => $element['#title_field_required'] == 1 ? [[get_called_class(), 'validateRequiredFields']] : [],
245     ];
246
247     return parent::process($element, $form_state, $form);
248   }
249
250   /**
251    * Validate callback for alt and title field, if the user wants them required.
252    *
253    * This is separated in a validate function instead of a #required flag to
254    * avoid being validated on the process callback.
255    */
256   public static function validateRequiredFields($element, FormStateInterface $form_state) {
257     // Only do validation if the function is triggered from other places than
258     // the image process form.
259     if (!in_array('file_managed_file_submit', $form_state->getTriggeringElement()['#submit'])) {
260       // If the image is not there, we do not check for empty values.
261       $parents = $element['#parents'];
262       $field = array_pop($parents);
263       $image_field = NestedArray::getValue($form_state->getUserInput(), $parents);
264       // We check for the array key, so that it can be NULL (like if the user
265       // submits the form without using the "upload" button).
266       if (!array_key_exists($field, $image_field)) {
267         return;
268       }
269     }
270     else {
271       $form_state->setLimitValidationErrors([]);
272     }
273   }
274
275   /**
276    * {@inheritdoc}
277    */
278   public function calculateDependencies() {
279     $dependencies = parent::calculateDependencies();
280     $style_id = $this->getSetting('preview_image_style');
281     /** @var \Drupal\image\ImageStyleInterface $style */
282     if ($style_id && $style = ImageStyle::load($style_id)) {
283       // If this widget uses a valid image style to display the preview of the
284       // uploaded image, add that image style configuration entity as dependency
285       // of this widget.
286       $dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
287     }
288     return $dependencies;
289   }
290
291   /**
292    * {@inheritdoc}
293    */
294   public function onDependencyRemoval(array $dependencies) {
295     $changed = parent::onDependencyRemoval($dependencies);
296     $style_id = $this->getSetting('preview_image_style');
297     /** @var \Drupal\image\ImageStyleInterface $style */
298     if ($style_id && $style = ImageStyle::load($style_id)) {
299       if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
300         /** @var \Drupal\image\ImageStyleStorageInterface $storage */
301         $storage = \Drupal::entityManager()->getStorage($style->getEntityTypeId());
302         $replacement_id = $storage->getReplacementId($style_id);
303         // If a valid replacement has been provided in the storage, replace the
304         // preview image style with the replacement.
305         if ($replacement_id && ImageStyle::load($replacement_id)) {
306           $this->setSetting('preview_image_style', $replacement_id);
307         }
308         // If there's no replacement or the replacement is invalid, disable the
309         // image preview.
310         else {
311           $this->setSetting('preview_image_style', '');
312         }
313         // Signal that the formatter plugin settings were updated.
314         $changed = TRUE;
315       }
316     }
317     return $changed;
318   }
319
320 }