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