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