FileBrowserWidget::getPersistentData()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
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' => 2];
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
      $entity_id = $entity->id();
257
258
      // Find the default description.
259
      $description = '';
260
      $display_field = $field_settings['display_default'];
261
      $alt = '';
262
      $title = '';
263
      $weight = $delta;
264
      $width = NULL;
265
      $height = NULL;
266
      foreach ($this->items as $item) {
267
        if ($item->target_id == $entity_id) {
268
          if ($field_type == 'file') {
269
            $description = $item->description;
270
            $display_field = $item->display;
271
          }
272
          elseif ($field_type == 'image') {
273
            $alt = $item->alt;
274
            $title = $item->title;
275
            $width = $item->width;
276
            $height = $item->height;
277
          }
278
          $weight = $item->_weight ?: $delta;
279
        }
280
      }
281
282
      $current[$entity_id] = [
283
        '#attributes' => [
284
          'class' => ['draggable'],
285
          'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity_id,
286
          'data-row-id' => $delta,
287
        ],
288
      ];
289
290
      // Provide a rendered entity if a view builder is available.
291
      if ($has_file_entity) {
292
        $current[$entity_id]['display'] = $this->entityTypeManager->getViewBuilder('file')->view($entity, $view_mode);
293
      }
294
      // For images, support a preview image style as an alternative.
295
      elseif ($field_type == 'image' && !empty($widget_settings['preview_image_style'])) {
296
        $uri = $entity->getFileUri();
297
        $current[$entity_id]['display'] = [
298
          '#weight' => -10,
299
          '#theme' => 'image_style',
300
          '#width' => $width,
301
          '#height' => $height,
302
          '#style_name' => $widget_settings['preview_image_style'],
303
          '#uri' => $uri,
304
        ];
305
      }
306
      // Assume that the file name is part of the preview output if
307
      // file entity is installed, do not show this column in that case.
308
      if (!$has_file_entity) {
309
        $current[$entity_id]['filename'] = ['#markup' => $entity->label()];
310
      }
311
      $current[$entity_id] += [
312
        'meta' => [
313
          'display_field' => [
314
            '#type' => 'checkbox',
315
            '#title' => $this->t('Include file in display'),
316
            '#default_value' => (bool) $display_field,
317
            '#access' => $field_type == 'file' && $field_settings['display_field'],
318
          ],
319
          'description' => [
320
            '#type' => $file_settings->get('description.type'),
321
            '#title' => $this->t('Description'),
322
            '#default_value' => $description,
323
            '#size' => 45,
324
            '#maxlength' => $file_settings->get('description.length'),
325
            '#description' => $this->t('The description may be used as the label of the link to the file.'),
326
            '#access' => $field_type == 'file' && $field_settings['description_field'],
327
          ],
328
          'alt' => [
329
            '#type' => 'textfield',
330
            '#title' => $this->t('Alternative text'),
331
            '#default_value' => $alt,
332
            '#size' => 45,
333
            '#maxlength' => 512,
334
            '#description' => $this->t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'),
335
            '#access' => $field_type == 'image' && $field_settings['alt_field'],
336
            '#required' => $field_type == 'image' && $field_settings['alt_field_required'],
337
          ],
338
          'title' => [
339
            '#type' => 'textfield',
340
            '#title' => $this->t('Title'),
341
            '#default_value' => $title,
342
            '#size' => 45,
343
            '#maxlength' => 1024,
344
            '#description' => $this->t('The title is used as a tool tip when the user hovers the mouse over the image.'),
345
            '#access' => $field_type == 'image' && $field_settings['title_field'],
346
            '#required' => $field_type == 'image' && $field_settings['title_field_required'],
347
          ],
348
        ],
349
        'edit_button' => [
350
          '#type' => 'submit',
351
          '#value' => $this->t('Edit'),
352
          '#ajax' => [
353
            'url' => Url::fromRoute('entity_browser.edit_form', ['entity_type' => $entity->getEntityTypeId(), 'entity' => $entity_id]),
354
            'options' => ['query' => ['details_id' => $details_id]],
355
          ],
356
          '#attributes' => [
357
            'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
358
            'data-row-id' => $delta,
359
          ],
360
          '#access' => $edit_button_access,
0 ignored issues
show
Bug introduced by
The variable $edit_button_access does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
361
        ],
362
        'remove_button' => [
363
          '#type' => 'submit',
364
          '#value' => $this->t('Remove'),
365
          '#ajax' => [
366
            'callback' => [get_class($this), 'updateWidgetCallback'],
367
            'wrapper' => $details_id,
368
          ],
369
          '#submit' => [[get_class($this), 'removeItemSubmit']],
370
          '#name' => $field_machine_name . '_remove_' . $entity_id . '_' . md5(json_encode($field_parents)),
371
          '#limit_validation_errors' => [array_merge($field_parents, [$field_machine_name, 'target_id'])],
372
          '#attributes' => [
373
            'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
374
            'data-row-id' => $delta,
375
          ],
376
          '#access' => (bool) $widget_settings['field_widget_remove'],
377
        ],
378
        '_weight' => [
379
          '#type' => 'weight',
380
          '#title' => $this->t('Weight for row @number', ['@number' => $delta + 1]),
381
          '#title_display' => 'invisible',
382
          // Note: this 'delta' is the FAPI #type 'weight' element's property.
383
          '#delta' => count($entities),
384
          '#default_value' => $weight,
385
          '#attributes' => ['class' => [$order_class]],
386
        ],
387
      ];
388
389
      $current['#attached']['library'][] = 'entity_browser/file_browser';
390
391
      $delta++;
392
    }
393
394
    return $current;
395
  }
396
397
  /**
398
   * {@inheritdoc}
399
   */
400
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
401
    $ids = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
402
    $return = [];
403
    foreach ($ids as $id) {
404
      $id = explode(':', $id)[1];
405
      if (is_array($values['current']) && isset($values['current'][$id])) {
406
        $item_values = [
407
          'target_id' => $id,
408
          '_weight' => $values['current'][$id]['_weight'],
409
        ];
410 View Code Duplication
        if ($this->fieldDefinition->getType() == 'file') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
411
          if (isset($values['current'][$id]['meta']['description'])) {
412
            $item_values['description'] = $values['current'][$id]['meta']['description'];
413
          }
414
          if ($this->fieldDefinition->getSetting('display_field') && isset($values['current'][$id]['meta']['display_field'])) {
415
            $item_values['display'] = $values['current'][$id]['meta']['display_field'];
416
          }
417
        }
418 View Code Duplication
        if ($this->fieldDefinition->getType() == 'image') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
          if (isset($values['current'][$id]['meta']['alt'])) {
420
            $item_values['alt'] = $values['current'][$id]['meta']['alt'];
421
          }
422
          if (isset($values['current'][$id]['meta']['title'])) {
423
            $item_values['title'] = $values['current'][$id]['meta']['title'];
424
          }
425
        }
426
        $return[] = $item_values;
427
      }
428
    }
429
430
    // Return ourself as the structure doesn't match the default.
431
    usort($return, function ($a, $b) {
432
      return SortArray::sortByKeyInt($a, $b, '_weight');
433
    });
434
435
    return array_values($return);
436
  }
437
438
  /**
439
   * Retrieves the upload validators for a file field.
440
   *
441
   * This is a combination of logic shared between the File and Image widgets.
442
   *
443
   * @param bool $upload
444
   *   Whether or not upload-specific validators should be returned.
445
   *
446
   * @return array
447
   *   An array suitable for passing to file_save_upload() or the file field
448
   *   element's '#upload_validators' property.
449
   */
450
  public function getFileValidators($upload = FALSE) {
451
    $validators = [];
452
    $settings = $this->fieldDefinition->getSettings();
453
454
    if ($upload) {
455
      // Cap the upload size according to the PHP limit.
456
      $max_filesize = Bytes::toInt(file_upload_max_size());
457
      if (!empty($settings['max_filesize'])) {
458
        $max_filesize = min($max_filesize, Bytes::toInt($settings['max_filesize']));
459
      }
460
      // There is always a file size limit due to the PHP server limit.
461
      $validators['file_validate_size'] = [$max_filesize];
462
    }
463
464
    // Images have expected defaults for file extensions.
465
    // See \Drupal\image\Plugin\Field\FieldWidget::formElement() for details.
466
    if ($this->fieldDefinition->getType() == 'image') {
467
      // If not using custom extension validation, ensure this is an image.
468
      $supported_extensions = ['png', 'gif', 'jpg', 'jpeg'];
469
      $extensions = isset($settings['file_extensions']) ? $settings['file_extensions'] : implode(' ', $supported_extensions);
470
      $extensions = array_intersect(explode(' ', $extensions), $supported_extensions);
471
      $validators['file_validate_extensions'] = [implode(' ', $extensions)];
472
473
      // Add resolution validation.
474
      if (!empty($settings['max_resolution']) || !empty($settings['min_resolution'])) {
475
        $validators['entity_browser_file_validate_image_resolution'] = [$settings['max_resolution'], $settings['min_resolution']];
476
      }
477
    }
478
    elseif (!empty($settings['file_extensions'])) {
479
      $validators['file_validate_extensions'] = [$settings['file_extensions']];
480
    }
481
482
    return $validators;
483
  }
484
485
  /**
486
   * {@inheritdoc}
487
   */
488
  protected function getPersistentData() {
489
    $data = parent::getPersistentData();
490
    $settings = $this->fieldDefinition->getSettings();
491
    // Add validators based on our current settings.
492
    $data['validators']['file'] = ['validators' => $this->getFileValidators()];
493
    // Provide context for widgets to enhance their configuration.
494
    $data['widget_context']['upload_location'] = $settings['uri_scheme'] . '://' . $settings['file_directory'];
495
    $data['widget_context']['upload_validators'] = $this->getFileValidators(TRUE);
496
    return $data;
497
  }
498
499
}
500