Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / entity_browser / src / Plugin / Field / FieldWidget / FileBrowserWidget.php
1 <?php
2
3 namespace Drupal\entity_browser\Plugin\Field\FieldWidget;
4
5 use Drupal\Component\Utility\Bytes;
6 use Drupal\Component\Utility\SortArray;
7 use Drupal\Core\Config\ConfigFactoryInterface;
8 use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
9 use Drupal\Core\Entity\EntityTypeManagerInterface;
10 use Drupal\Core\Extension\ModuleHandlerInterface;
11 use Drupal\Core\Field\FieldDefinitionInterface;
12 use Drupal\Core\Field\FieldItemListInterface;
13 use Drupal\Core\Form\FormStateInterface;
14 use Drupal\Core\Url;
15 use Drupal\entity_browser\FieldWidgetDisplayManager;
16 use Drupal\image\Entity\ImageStyle;
17 use Symfony\Component\DependencyInjection\ContainerInterface;
18 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19 use Drupal\Core\Session\AccountInterface;
20
21 /**
22  * Entity browser file widget.
23  *
24  * @FieldWidget(
25  *   id = "entity_browser_file",
26  *   label = @Translation("Entity browser"),
27  *   provider = "entity_browser",
28  *   multiple_values = TRUE,
29  *   field_types = {
30  *     "file",
31  *     "image"
32  *   }
33  * )
34  */
35 class FileBrowserWidget extends EntityReferenceBrowserWidget {
36
37   /**
38    * Due to the table structure, this widget has a different depth.
39    *
40    * @var int
41    */
42   protected static $deleteDepth = 3;
43
44   /**
45    * A list of currently edited items. Used to determine alt/title values.
46    *
47    * @var \Drupal\Core\Field\FieldItemListInterface
48    */
49   protected $items;
50
51   /**
52    * The config factory service.
53    *
54    * @var \Drupal\Core\Config\ConfigFactoryInterface
55    */
56   protected $configFactory;
57
58   /**
59    * The display repository service.
60    *
61    * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
62    */
63   protected $displayRepository;
64
65   /**
66    * Constructs widget plugin.
67    *
68    * @param string $plugin_id
69    *   The plugin_id for the plugin instance.
70    * @param mixed $plugin_definition
71    *   The plugin implementation definition.
72    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
73    *   The definition of the field to which the widget is associated.
74    * @param array $settings
75    *   The widget settings.
76    * @param array $third_party_settings
77    *   Any third party settings.
78    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
79    *   Entity type manager service.
80    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
81    *   Event dispatcher.
82    * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
83    *   Field widget display plugin manager.
84    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
85    *   The config factory.
86    * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository
87    *   The entity display repository service.
88    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
89    *   The module handler service.
90    * @param \Drupal\Core\Session\AccountInterface $current_user
91    *   The current user.
92    */
93   public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, FieldWidgetDisplayManager $field_display_manager, ConfigFactoryInterface $config_factory, EntityDisplayRepositoryInterface $display_repository, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
94     parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $entity_type_manager, $event_dispatcher, $field_display_manager, $module_handler, $current_user);
95     $this->entityTypeManager = $entity_type_manager;
96     $this->fieldDisplayManager = $field_display_manager;
97     $this->configFactory = $config_factory;
98     $this->displayRepository = $display_repository;
99   }
100
101   /**
102    * {@inheritdoc}
103    */
104   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
105     return new static(
106       $plugin_id,
107       $plugin_definition,
108       $configuration['field_definition'],
109       $configuration['settings'],
110       $configuration['third_party_settings'],
111       $container->get('entity_type.manager'),
112       $container->get('event_dispatcher'),
113       $container->get('plugin.manager.entity_browser.field_widget_display'),
114       $container->get('config.factory'),
115       $container->get('entity_display.repository'),
116       $container->get('module_handler'),
117       $container->get('current_user')
118     );
119   }
120
121   /**
122    * {@inheritdoc}
123    */
124   public static function defaultSettings() {
125     $settings = parent::defaultSettings();
126
127     // These settings are hidden.
128     unset($settings['field_widget_display']);
129     unset($settings['field_widget_display_settings']);
130
131     $settings['view_mode'] = 'default';
132     $settings['preview_image_style'] = 'thumbnail';
133
134     return $settings;
135   }
136
137   /**
138    * {@inheritdoc}
139    */
140   public function settingsForm(array $form, FormStateInterface $form_state) {
141     $element = parent::settingsForm($form, $form_state);
142     $has_file_entity = $this->moduleHandler->moduleExists('file_entity');
143
144     $element['field_widget_display']['#access'] = FALSE;
145     $element['field_widget_display_settings']['#access'] = FALSE;
146
147     $element['view_mode'] = [
148       '#title' => $this->t('File view mode'),
149       '#type' => 'select',
150       '#default_value' => $this->getSetting('view_mode'),
151       '#options' => $this->displayRepository->getViewModeOptions('file'),
152       '#access' => $has_file_entity,
153     ];
154
155     $element['preview_image_style'] = [
156       '#title' => $this->t('Preview image style'),
157       '#type' => 'select',
158       '#options' => image_style_options(FALSE),
159       '#default_value' => $this->getSetting('preview_image_style'),
160       '#description' => $this->t('The preview image will be shown while editing the content. Only relevant if using the default file view mode.'),
161       '#weight' => 15,
162       '#access' => !$has_file_entity && $this->fieldDefinition->getType() == 'image',
163     ];
164
165     return $element;
166   }
167
168   /**
169    * {@inheritdoc}
170    */
171   public function settingsSummary() {
172     $summary = $this->summaryBase();
173     $view_mode = $this->getSetting('view_mode');
174     $image_style_setting = $this->getSetting('preview_image_style');
175
176     if ($this->moduleHandler->moduleExists('file_entity')) {
177       $preview_image_style = $this->t('Preview with @view_mode', ['@view_mode' => $view_mode]);
178     }
179     // Styles could be lost because of enabled/disabled modules that defines
180     // their styles in code.
181     elseif ($this->fieldDefinition->getType() == 'image' && $image_style = ImageStyle::load($image_style_setting)) {
182       $preview_image_style = $this->t('Preview image style: @style', ['@style' => $image_style->label()]);
183     }
184     else {
185       $preview_image_style = $this->t('No preview image');
186     }
187     array_unshift($summary, $preview_image_style);
188
189     return $summary;
190   }
191
192   /**
193    * {@inheritdoc}
194    */
195   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
196     $this->items = $items;
197     return parent::formElement($items, $delta, $element, $form, $form_state);
198   }
199
200   /**
201    * {@inheritdoc}
202    */
203   protected function displayCurrentSelection($details_id, $field_parents, $entities) {
204     $field_type = $this->fieldDefinition->getType();
205     $field_settings = $this->fieldDefinition->getSettings();
206     $field_machine_name = $this->fieldDefinition->getName();
207     $file_settings = $this->configFactory->get('file.settings');
208     $widget_settings = $this->getSettings();
209     $view_mode = $widget_settings['view_mode'];
210     $can_edit = (bool) $widget_settings['field_widget_edit'];
211     $has_file_entity = $this->moduleHandler->moduleExists('file_entity');
212
213     $delta = 0;
214
215     $order_class = $field_machine_name . '-delta-order';
216
217     $current = [
218       '#type' => 'table',
219       '#empty' => $this->t('No files yet'),
220       '#attributes' => ['class' => ['entities-list']],
221       '#tabledrag' => [
222         [
223           'action' => 'order',
224           'relationship' => 'sibling',
225           'group' => $order_class,
226         ],
227       ],
228     ];
229
230     if ($has_file_entity || $field_type == 'image' && !empty($widget_settings['preview_image_style'])) {
231       // Had the preview column if we have one.
232       $current['#header'][] = $this->t('Preview');
233     }
234
235     // Add the filename if there is no view builder.
236     if (!$has_file_entity) {
237       $current['#header'][] = $this->t('Filename');
238     }
239
240     // Add the remaining columns.
241     $current['#header'][] = $this->t('Metadata');
242     $current['#header'][] = ['data' => $this->t('Operations'), 'colspan' => 3];
243     $current['#header'][] = $this->t('Order', [], ['context' => 'Sort order']);
244
245     /** @var \Drupal\file\FileInterface[] $entities */
246     foreach ($entities as $entity) {
247       // Check to see if this entity has an edit form. If not, the edit button
248       // will only throw an exception.
249       if (!$entity->getEntityType()->getFormClass('edit')) {
250         $edit_button_access = FALSE;
251       }
252       elseif ($has_file_entity) {
253         $edit_button_access = $can_edit && $entity->access('update', $this->currentUser);
254       }
255
256       // The "Replace" button will only be shown if this setting is enabled in
257       // the widget, and there is only one entity in the current selection.
258       $replace_button_access = $this->getSetting('field_widget_replace') && (count($entities) === 1);
259
260       $entity_id = $entity->id();
261
262       // Find the default description.
263       $description = '';
264       $display_field = $field_settings['display_default'];
265       $alt = '';
266       $title = '';
267       $weight = $delta;
268       $width = NULL;
269       $height = NULL;
270       foreach ($this->items as $item) {
271         if ($item->target_id == $entity_id) {
272           if ($field_type == 'file') {
273             $description = $item->description;
274             $display_field = $item->display;
275           }
276           elseif ($field_type == 'image') {
277             $alt = $item->alt;
278             $title = $item->title;
279             $width = $item->width;
280             $height = $item->height;
281           }
282           $weight = $item->_weight ?: $delta;
283         }
284       }
285
286       $current[$entity_id] = [
287         '#attributes' => [
288           'class' => ['draggable'],
289           'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity_id,
290           'data-row-id' => $delta,
291         ],
292       ];
293
294       // Provide a rendered entity if a view builder is available.
295       if ($has_file_entity) {
296         $current[$entity_id]['display'] = $this->entityTypeManager->getViewBuilder('file')->view($entity, $view_mode);
297       }
298       // For images, support a preview image style as an alternative.
299       elseif ($field_type == 'image' && !empty($widget_settings['preview_image_style'])) {
300         $uri = $entity->getFileUri();
301         $current[$entity_id]['display'] = [
302           '#weight' => -10,
303           '#theme' => 'image_style',
304           '#width' => $width,
305           '#height' => $height,
306           '#style_name' => $widget_settings['preview_image_style'],
307           '#uri' => $uri,
308         ];
309       }
310       // Assume that the file name is part of the preview output if
311       // file entity is installed, do not show this column in that case.
312       if (!$has_file_entity) {
313         $current[$entity_id]['filename'] = ['#markup' => $entity->label()];
314       }
315       $current[$entity_id] += [
316         'meta' => [
317           'display_field' => [
318             '#type' => 'checkbox',
319             '#title' => $this->t('Include file in display'),
320             '#default_value' => (bool) $display_field,
321             '#access' => $field_type == 'file' && $field_settings['display_field'],
322           ],
323           'description' => [
324             '#type' => $file_settings->get('description.type'),
325             '#title' => $this->t('Description'),
326             '#default_value' => $description,
327             '#size' => 45,
328             '#maxlength' => $file_settings->get('description.length'),
329             '#description' => $this->t('The description may be used as the label of the link to the file.'),
330             '#access' => $field_type == 'file' && $field_settings['description_field'],
331           ],
332           'alt' => [
333             '#type' => 'textfield',
334             '#title' => $this->t('Alternative text'),
335             '#default_value' => $alt,
336             '#size' => 45,
337             '#maxlength' => 512,
338             '#description' => $this->t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
339             '#access' => $field_type == 'image' && $field_settings['alt_field'],
340             '#required' => $field_type == 'image' && $field_settings['alt_field_required'],
341           ],
342           'title' => [
343             '#type' => 'textfield',
344             '#title' => $this->t('Title'),
345             '#default_value' => $title,
346             '#size' => 45,
347             '#maxlength' => 1024,
348             '#description' => $this->t('The title is used as a tool tip when the user hovers the mouse over the image.'),
349             '#access' => $field_type == 'image' && $field_settings['title_field'],
350             '#required' => $field_type == 'image' && $field_settings['title_field_required'],
351           ],
352         ],
353         'edit_button' => [
354           '#type' => 'submit',
355           '#value' => $this->t('Edit'),
356           '#ajax' => [
357             'url' => Url::fromRoute('entity_browser.edit_form', ['entity_type' => $entity->getEntityTypeId(), 'entity' => $entity_id]),
358             'options' => ['query' => ['details_id' => $details_id]],
359           ],
360           '#attributes' => [
361             'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
362             'data-row-id' => $delta,
363             'class' => ['edit-button'],
364           ],
365           '#access' => $edit_button_access,
366         ],
367         'replace_button' => [
368           '#type' => 'submit',
369           '#value' => $this->t('Replace'),
370           '#ajax' => [
371             'callback' => [get_class($this), 'updateWidgetCallback'],
372             'wrapper' => $details_id,
373           ],
374           '#submit' => [[get_class($this), 'removeItemSubmit']],
375           '#name' => $field_machine_name . '_replace_' . $entity_id . '_' . md5(json_encode($field_parents)),
376           '#limit_validation_errors' => [array_merge($field_parents, [$field_machine_name, 'target_id'])],
377           '#attributes' => [
378             'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
379             'data-row-id' => $delta,
380             'class' => ['replace-button'],
381           ],
382           '#access' => $replace_button_access,
383         ],
384         'remove_button' => [
385           '#type' => 'submit',
386           '#value' => $this->t('Remove'),
387           '#ajax' => [
388             'callback' => [get_class($this), 'updateWidgetCallback'],
389             'wrapper' => $details_id,
390           ],
391           '#submit' => [[get_class($this), 'removeItemSubmit']],
392           '#name' => $field_machine_name . '_remove_' . $entity_id . '_' . md5(json_encode($field_parents)),
393           '#limit_validation_errors' => [array_merge($field_parents, [$field_machine_name, 'target_id'])],
394           '#attributes' => [
395             'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
396             'data-row-id' => $delta,
397             'class' => ['remove-button'],
398           ],
399           '#access' => (bool) $widget_settings['field_widget_remove'],
400         ],
401         '_weight' => [
402           '#type' => 'weight',
403           '#title' => $this->t('Weight for row @number', ['@number' => $delta + 1]),
404           '#title_display' => 'invisible',
405           // Note: this 'delta' is the FAPI #type 'weight' element's property.
406           '#delta' => count($entities),
407           '#default_value' => $weight,
408           '#attributes' => ['class' => [$order_class]],
409         ],
410       ];
411
412       $current['#attached']['library'][] = 'entity_browser/file_browser';
413
414       $delta++;
415     }
416
417     return $current;
418   }
419
420   /**
421    * {@inheritdoc}
422    */
423   public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
424     $ids = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
425     $return = [];
426     foreach ($ids as $id) {
427       $id = explode(':', $id)[1];
428       if (is_array($values['current']) && isset($values['current'][$id])) {
429         $item_values = [
430           'target_id' => $id,
431           '_weight' => $values['current'][$id]['_weight'],
432         ];
433         if ($this->fieldDefinition->getType() == 'file') {
434           if (isset($values['current'][$id]['meta']['description'])) {
435             $item_values['description'] = $values['current'][$id]['meta']['description'];
436           }
437           if ($this->fieldDefinition->getSetting('display_field') && isset($values['current'][$id]['meta']['display_field'])) {
438             $item_values['display'] = $values['current'][$id]['meta']['display_field'];
439           }
440         }
441         if ($this->fieldDefinition->getType() == 'image') {
442           if (isset($values['current'][$id]['meta']['alt'])) {
443             $item_values['alt'] = $values['current'][$id]['meta']['alt'];
444           }
445           if (isset($values['current'][$id]['meta']['title'])) {
446             $item_values['title'] = $values['current'][$id]['meta']['title'];
447           }
448         }
449         $return[] = $item_values;
450       }
451     }
452
453     // Return ourself as the structure doesn't match the default.
454     usort($return, function ($a, $b) {
455       return SortArray::sortByKeyInt($a, $b, '_weight');
456     });
457
458     return array_values($return);
459   }
460
461   /**
462    * Retrieves the upload validators for a file field.
463    *
464    * This is a combination of logic shared between the File and Image widgets.
465    *
466    * @param bool $upload
467    *   Whether or not upload-specific validators should be returned.
468    *
469    * @return array
470    *   An array suitable for passing to file_save_upload() or the file field
471    *   element's '#upload_validators' property.
472    */
473   public function getFileValidators($upload = FALSE) {
474     $validators = [];
475     $settings = $this->fieldDefinition->getSettings();
476
477     if ($upload) {
478       // Cap the upload size according to the PHP limit.
479       $max_filesize = Bytes::toInt(file_upload_max_size());
480       if (!empty($settings['max_filesize'])) {
481         $max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize']));
482       }
483       // There is always a file size limit due to the PHP server limit.
484       $validators['file_validate_size'] = [$max_filesize];
485     }
486
487     // Images have expected defaults for file extensions.
488     // See \Drupal\image\Plugin\Field\FieldWidget::formElement() for details.
489     if ($this->fieldDefinition->getType() == 'image') {
490       // If not using custom extension validation, ensure this is an image.
491       $supported_extensions = ['png', 'gif', 'jpg', 'jpeg'];
492       $extensions = isset($settings['file_extensions']) ? $settings['file_extensions'] : implode(' ', $supported_extensions);
493       $extensions = array_intersect(explode(' ', $extensions), $supported_extensions);
494       $validators['file_validate_extensions'] = [implode(' ', $extensions)];
495
496       // Add resolution validation.
497       if (!empty($settings['max_resolution']) || !empty($settings['min_resolution'])) {
498         $validators['entity_browser_file_validate_image_resolution'] = [$settings['max_resolution'], $settings['min_resolution']];
499       }
500     }
501     elseif (!empty($settings['file_extensions'])) {
502       $validators['file_validate_extensions'] = [$settings['file_extensions']];
503     }
504
505     return $validators;
506   }
507
508   /**
509    * {@inheritdoc}
510    */
511   protected function getPersistentData() {
512     $data = parent::getPersistentData();
513     $settings = $this->fieldDefinition->getSettings();
514     // Add validators based on our current settings.
515     $data['validators']['file'] = ['validators' => $this->getFileValidators()];
516     // Provide context for widgets to enhance their configuration.
517     $data['widget_context']['upload_location'] = $settings['uri_scheme'] . '://' . $settings['file_directory'];
518     $data['widget_context']['upload_validators'] = $this->getFileValidators(TRUE);
519     return $data;
520   }
521
522 }