Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / modules / image / src / Plugin / Field / FieldType / ImageItem.php
1 <?php
2
3 namespace Drupal\image\Plugin\Field\FieldType;
4
5 use Drupal\Component\Utility\Random;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Field\FieldDefinitionInterface;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
11 use Drupal\Core\TypedData\DataDefinition;
12 use Drupal\file\Entity\File;
13 use Drupal\file\Plugin\Field\FieldType\FileItem;
14
15 /**
16  * Plugin implementation of the 'image' field type.
17  *
18  * @FieldType(
19  *   id = "image",
20  *   label = @Translation("Image"),
21  *   description = @Translation("This field stores the ID of an image file as an integer value."),
22  *   category = @Translation("Reference"),
23  *   default_widget = "image_image",
24  *   default_formatter = "image",
25  *   column_groups = {
26  *     "file" = {
27  *       "label" = @Translation("File"),
28  *       "columns" = {
29  *         "target_id", "width", "height"
30  *       },
31  *       "require_all_groups_for_translation" = TRUE
32  *     },
33  *     "alt" = {
34  *       "label" = @Translation("Alt"),
35  *       "translatable" = TRUE
36  *     },
37  *     "title" = {
38  *       "label" = @Translation("Title"),
39  *       "translatable" = TRUE
40  *     },
41  *   },
42  *   list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
43  *   constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
44  * )
45  */
46 class ImageItem extends FileItem {
47
48   /**
49    * The entity manager.
50    *
51    * @var \Drupal\Core\Entity\EntityManagerInterface
52    */
53   protected $entityManager;
54
55   /**
56    * {@inheritdoc}
57    */
58   public static function defaultStorageSettings() {
59     return [
60       'default_image' => [
61         'uuid' => NULL,
62         'alt' => '',
63         'title' => '',
64         'width' => NULL,
65         'height' => NULL,
66       ],
67     ] + parent::defaultStorageSettings();
68   }
69
70   /**
71    * {@inheritdoc}
72    */
73   public static function defaultFieldSettings() {
74     $settings = [
75       'file_extensions' => 'png gif jpg jpeg',
76       'alt_field' => 1,
77       'alt_field_required' => 1,
78       'title_field' => 0,
79       'title_field_required' => 0,
80       'max_resolution' => '',
81       'min_resolution' => '',
82       'default_image' => [
83         'uuid' => NULL,
84         'alt' => '',
85         'title' => '',
86         'width' => NULL,
87         'height' => NULL,
88       ],
89     ] + parent::defaultFieldSettings();
90
91     unset($settings['description_field']);
92     return $settings;
93   }
94
95   /**
96    * {@inheritdoc}
97    */
98   public static function schema(FieldStorageDefinitionInterface $field_definition) {
99     return [
100       'columns' => [
101         'target_id' => [
102           'description' => 'The ID of the file entity.',
103           'type' => 'int',
104           'unsigned' => TRUE,
105         ],
106         'alt' => [
107           'description' => "Alternative image text, for the image's 'alt' attribute.",
108           'type' => 'varchar',
109           'length' => 512,
110         ],
111         'title' => [
112           'description' => "Image title text, for the image's 'title' attribute.",
113           'type' => 'varchar',
114           'length' => 1024,
115         ],
116         'width' => [
117           'description' => 'The width of the image in pixels.',
118           'type' => 'int',
119           'unsigned' => TRUE,
120         ],
121         'height' => [
122           'description' => 'The height of the image in pixels.',
123           'type' => 'int',
124           'unsigned' => TRUE,
125         ],
126       ],
127       'indexes' => [
128         'target_id' => ['target_id'],
129       ],
130       'foreign keys' => [
131         'target_id' => [
132           'table' => 'file_managed',
133           'columns' => ['target_id' => 'fid'],
134         ],
135       ],
136     ];
137   }
138
139   /**
140    * {@inheritdoc}
141    */
142   public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
143     $properties = parent::propertyDefinitions($field_definition);
144
145     unset($properties['display']);
146     unset($properties['description']);
147
148     $properties['alt'] = DataDefinition::create('string')
149       ->setLabel(t('Alternative text'))
150       ->setDescription(t("Alternative image text, for the image's 'alt' attribute."));
151
152     $properties['title'] = DataDefinition::create('string')
153       ->setLabel(t('Title'))
154       ->setDescription(t("Image title text, for the image's 'title' attribute."));
155
156     $properties['width'] = DataDefinition::create('integer')
157       ->setLabel(t('Width'))
158       ->setDescription(t('The width of the image in pixels.'));
159
160     $properties['height'] = DataDefinition::create('integer')
161       ->setLabel(t('Height'))
162       ->setDescription(t('The height of the image in pixels.'));
163
164     return $properties;
165   }
166
167   /**
168    * {@inheritdoc}
169    */
170   public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
171     $element = [];
172
173     // We need the field-level 'default_image' setting, and $this->getSettings()
174     // will only provide the instance-level one, so we need to explicitly fetch
175     // the field.
176     $settings = $this->getFieldDefinition()->getFieldStorageDefinition()->getSettings();
177
178     $scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
179     $element['uri_scheme'] = [
180       '#type' => 'radios',
181       '#title' => t('Upload destination'),
182       '#options' => $scheme_options,
183       '#default_value' => $settings['uri_scheme'],
184       '#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
185     ];
186
187     // Add default_image element.
188     static::defaultImageForm($element, $settings);
189     $element['default_image']['#description'] = t('If no image is uploaded, this image will be shown on display.');
190
191     return $element;
192   }
193
194   /**
195    * {@inheritdoc}
196    */
197   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
198     // Get base form from FileItem.
199     $element = parent::fieldSettingsForm($form, $form_state);
200
201     $settings = $this->getSettings();
202
203     // Add maximum and minimum resolution settings.
204     $max_resolution = explode('x', $settings['max_resolution']) + ['', ''];
205     $element['max_resolution'] = [
206       '#type' => 'item',
207       '#title' => t('Maximum image resolution'),
208       '#element_validate' => [[get_class($this), 'validateResolution']],
209       '#weight' => 4.1,
210       '#field_prefix' => '<div class="container-inline">',
211       '#field_suffix' => '</div>',
212       '#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="http://wikipedia.org/wiki/Exchangeable_image_file_format">EXIF data</a> in the image.'),
213     ];
214     $element['max_resolution']['x'] = [
215       '#type' => 'number',
216       '#title' => t('Maximum width'),
217       '#title_display' => 'invisible',
218       '#default_value' => $max_resolution[0],
219       '#min' => 1,
220       '#field_suffix' => ' Ã— ',
221     ];
222     $element['max_resolution']['y'] = [
223       '#type' => 'number',
224       '#title' => t('Maximum height'),
225       '#title_display' => 'invisible',
226       '#default_value' => $max_resolution[1],
227       '#min' => 1,
228       '#field_suffix' => ' ' . t('pixels'),
229     ];
230
231     $min_resolution = explode('x', $settings['min_resolution']) + ['', ''];
232     $element['min_resolution'] = [
233       '#type' => 'item',
234       '#title' => t('Minimum image resolution'),
235       '#element_validate' => [[get_class($this), 'validateResolution']],
236       '#weight' => 4.2,
237       '#field_prefix' => '<div class="container-inline">',
238       '#field_suffix' => '</div>',
239       '#description' => t('The minimum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'),
240     ];
241     $element['min_resolution']['x'] = [
242       '#type' => 'number',
243       '#title' => t('Minimum width'),
244       '#title_display' => 'invisible',
245       '#default_value' => $min_resolution[0],
246       '#min' => 1,
247       '#field_suffix' => ' Ã— ',
248     ];
249     $element['min_resolution']['y'] = [
250       '#type' => 'number',
251       '#title' => t('Minimum height'),
252       '#title_display' => 'invisible',
253       '#default_value' => $min_resolution[1],
254       '#min' => 1,
255       '#field_suffix' => ' ' . t('pixels'),
256     ];
257
258     // Remove the description option.
259     unset($element['description_field']);
260
261     // Add title and alt configuration options.
262     $element['alt_field'] = [
263       '#type' => 'checkbox',
264       '#title' => t('Enable <em>Alt</em> field'),
265       '#default_value' => $settings['alt_field'],
266       '#description' => t('The alt attribute may be used by search engines, screen readers, and when the image cannot be loaded. Enabling this field is recommended.'),
267       '#weight' => 9,
268     ];
269     $element['alt_field_required'] = [
270       '#type' => 'checkbox',
271       '#title' => t('<em>Alt</em> field required'),
272       '#default_value' => $settings['alt_field_required'],
273       '#description' => t('Making this field required is recommended.'),
274       '#weight' => 10,
275       '#states' => [
276         'visible' => [
277           ':input[name="settings[alt_field]"]' => ['checked' => TRUE],
278         ],
279       ],
280     ];
281     $element['title_field'] = [
282       '#type' => 'checkbox',
283       '#title' => t('Enable <em>Title</em> field'),
284       '#default_value' => $settings['title_field'],
285       '#description' => t('The title attribute is used as a tooltip when the mouse hovers over the image. Enabling this field is not recommended as it can cause problems with screen readers.'),
286       '#weight' => 11,
287     ];
288     $element['title_field_required'] = [
289       '#type' => 'checkbox',
290       '#title' => t('<em>Title</em> field required'),
291       '#default_value' => $settings['title_field_required'],
292       '#weight' => 12,
293       '#states' => [
294         'visible' => [
295           ':input[name="settings[title_field]"]' => ['checked' => TRUE],
296         ],
297       ],
298     ];
299
300     // Add default_image element.
301     static::defaultImageForm($element, $settings);
302     $element['default_image']['#description'] = t("If no image is uploaded, this image will be shown on display and will override the field's default image.");
303
304     return $element;
305   }
306
307   /**
308    * {@inheritdoc}
309    */
310   public function preSave() {
311     parent::preSave();
312
313     $width = $this->width;
314     $height = $this->height;
315
316     // Determine the dimensions if necessary.
317     if ($this->entity && $this->entity instanceof EntityInterface) {
318       if (empty($width) || empty($height)) {
319         $image = \Drupal::service('image.factory')->get($this->entity->getFileUri());
320         if ($image->isValid()) {
321           $this->width = $image->getWidth();
322           $this->height = $image->getHeight();
323         }
324       }
325     }
326     else {
327       trigger_error(sprintf("Missing file with ID %s.", $this->target_id), E_USER_WARNING);
328     }
329   }
330
331   /**
332    * {@inheritdoc}
333    */
334   public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
335     $random = new Random();
336     $settings = $field_definition->getSettings();
337     static $images = [];
338
339     $min_resolution = empty($settings['min_resolution']) ? '100x100' : $settings['min_resolution'];
340     $max_resolution = empty($settings['max_resolution']) ? '600x600' : $settings['max_resolution'];
341     $extensions = array_intersect(explode(' ', $settings['file_extensions']), ['png', 'gif', 'jpg', 'jpeg']);
342     $extension = array_rand(array_combine($extensions, $extensions));
343     // Generate a max of 5 different images.
344     if (!isset($images[$extension][$min_resolution][$max_resolution]) || count($images[$extension][$min_resolution][$max_resolution]) <= 5) {
345       $tmp_file = drupal_tempnam('temporary://', 'generateImage_');
346       $destination = $tmp_file . '.' . $extension;
347       file_unmanaged_move($tmp_file, $destination);
348       if ($path = $random->image(\Drupal::service('file_system')->realpath($destination), $min_resolution, $max_resolution)) {
349         $image = File::create();
350         $image->setFileUri($path);
351         $image->setOwnerId(\Drupal::currentUser()->id());
352         $image->setMimeType(\Drupal::service('file.mime_type.guesser')->guess($path));
353         $image->setFileName(drupal_basename($path));
354         $destination_dir = static::doGetUploadLocation($settings);
355         file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
356         $destination = $destination_dir . '/' . basename($path);
357         $file = file_move($image, $destination);
358         $images[$extension][$min_resolution][$max_resolution][$file->id()] = $file;
359       }
360       else {
361         return [];
362       }
363     }
364     else {
365       // Select one of the images we've already generated for this field.
366       $image_index = array_rand($images[$extension][$min_resolution][$max_resolution]);
367       $file = $images[$extension][$min_resolution][$max_resolution][$image_index];
368     }
369
370     list($width, $height) = getimagesize($file->getFileUri());
371     $values = [
372       'target_id' => $file->id(),
373       'alt' => $random->sentences(4),
374       'title' => $random->sentences(4),
375       'width' => $width,
376       'height' => $height,
377     ];
378     return $values;
379   }
380
381   /**
382    * Element validate function for resolution fields.
383    */
384   public static function validateResolution($element, FormStateInterface $form_state) {
385     if (!empty($element['x']['#value']) || !empty($element['y']['#value'])) {
386       foreach (['x', 'y'] as $dimension) {
387         if (!$element[$dimension]['#value']) {
388           // We expect the field name placeholder value to be wrapped in t()
389           // here, so it won't be escaped again as it's already marked safe.
390           $form_state->setError($element[$dimension], t('Both a height and width value must be specified in the @name field.', ['@name' => $element['#title']]));
391           return;
392         }
393       }
394       $form_state->setValueForElement($element, $element['x']['#value'] . 'x' . $element['y']['#value']);
395     }
396     else {
397       $form_state->setValueForElement($element, '');
398     }
399   }
400
401   /**
402    * Builds the default_image details element.
403    *
404    * @param array $element
405    *   The form associative array passed by reference.
406    * @param array $settings
407    *   The field settings array.
408    */
409   protected function defaultImageForm(array &$element, array $settings) {
410     $element['default_image'] = [
411       '#type' => 'details',
412       '#title' => t('Default image'),
413       '#open' => TRUE,
414     ];
415     // Convert the stored UUID to a FID.
416     $fids = [];
417     $uuid = $settings['default_image']['uuid'];
418     if ($uuid && ($file = $this->getEntityManager()->loadEntityByUuid('file', $uuid))) {
419       $fids[0] = $file->id();
420     }
421     $element['default_image']['uuid'] = [
422       '#type' => 'managed_file',
423       '#title' => t('Image'),
424       '#description' => t('Image to be shown if no image is uploaded.'),
425       '#default_value' => $fids,
426       '#upload_location' => $settings['uri_scheme'] . '://default_images/',
427       '#element_validate' => [
428         '\Drupal\file\Element\ManagedFile::validateManagedFile',
429         [get_class($this), 'validateDefaultImageForm'],
430       ],
431       '#upload_validators' => $this->getUploadValidators(),
432     ];
433     $element['default_image']['alt'] = [
434       '#type' => 'textfield',
435       '#title' => t('Alternative text'),
436       '#description' => t('This text will be used by screen readers, search engines, and when the image cannot be loaded.'),
437       '#default_value' => $settings['default_image']['alt'],
438       '#maxlength' => 512,
439     ];
440     $element['default_image']['title'] = [
441       '#type' => 'textfield',
442       '#title' => t('Title'),
443       '#description' => t('The title attribute is used as a tooltip when the mouse hovers over the image.'),
444       '#default_value' => $settings['default_image']['title'],
445       '#maxlength' => 1024,
446     ];
447     $element['default_image']['width'] = [
448       '#type' => 'value',
449       '#value' => $settings['default_image']['width'],
450     ];
451     $element['default_image']['height'] = [
452       '#type' => 'value',
453       '#value' => $settings['default_image']['height'],
454     ];
455   }
456
457   /**
458    * Validates the managed_file element for the default Image form.
459    *
460    * This function ensures the fid is a scalar value and not an array. It is
461    * assigned as a #element_validate callback in
462    * \Drupal\image\Plugin\Field\FieldType\ImageItem::defaultImageForm().
463    *
464    * @param array $element
465    *   The form element to process.
466    * @param \Drupal\Core\Form\FormStateInterface $form_state
467    *   The form state.
468    */
469   public static function validateDefaultImageForm(array &$element, FormStateInterface $form_state) {
470     // Consolidate the array value of this field to a single FID as #extended
471     // for default image is not TRUE and this is a single value.
472     if (isset($element['fids']['#value'][0])) {
473       $value = $element['fids']['#value'][0];
474       // Convert the file ID to a uuid.
475       if ($file = \Drupal::entityManager()->getStorage('file')->load($value)) {
476         $value = $file->uuid();
477       }
478     }
479     else {
480       $value = '';
481     }
482     $form_state->setValueForElement($element, $value);
483   }
484
485   /**
486    * {@inheritdoc}
487    */
488   public function isDisplayed() {
489     // Image items do not have per-item visibility settings.
490     return TRUE;
491   }
492
493   /**
494    * Gets the entity manager.
495    *
496    * @return \Drupal\Core\Entity\EntityManagerInterface
497    */
498   protected function getEntityManager() {
499     if (!isset($this->entityManager)) {
500       $this->entityManager = \Drupal::entityManager();
501     }
502     return $this->entityManager;
503   }
504
505 }