media_library.module 19.4 KB
Newer Older
1
2
3
4
5
6
7
<?php

/**
 * @file
 * Contains hook implementations for the media_library module.
 */

8
9
10
11
12
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
13
14
15
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
16
use Drupal\Core\Routing\RouteMatchInterface;
17
use Drupal\Core\Session\AccountInterface;
18
use Drupal\Core\Template\Attribute;
19
use Drupal\Core\Url;
20
21
22
23
use Drupal\image\Entity\ImageStyle;
use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\media\MediaTypeForm;
use Drupal\media\MediaTypeInterface;
24
use Drupal\media_library\Form\FileUploadForm;
25
use Drupal\media_library\Form\OEmbedForm;
26
use Drupal\media_library\MediaLibraryState;
27
use Drupal\views\Form\ViewsForm;
28
29
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\ViewExecutable;
30
31
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Component\Serialization\Json;
32
33
34
35
36
37
38
39
40
41

/**
 * Implements hook_help().
 *
 * @todo Update in https://www.drupal.org/project/drupal/issues/2964789
 */
function media_library_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.media_library':
      $output = '<h3>' . t('About') . '</h3>';
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
      $output .= '<p>' . t('The Media Library module provides a rich, visual interface for managing media, and allows media to be reused in entity reference fields or embedded into text content. It overrides the <a href=":media-collection">media administration page</a>, allowing users to toggle between the existing table-style interface and a new grid-style interface for browsing and performing administrative operations on media.', [
        ':media-collection' => Url::fromRoute('entity.media.collection')->toString(),
      ]) . '</p>';
      $output .= '<p>' . t('To learn more about media management, begin by reviewing the <a href=":media-help">documentation for the Media module</a>. For more information about the media library and related functionality, see the <a href=":media-library-handbook">online documentation for the Media Library module</a>.', [
        ':media-help' => Url::fromRoute('help.page', ['name' => 'media'])->toString(),
        ':media-library-handbook' => 'https://www.drupal.org/docs/8/core/modules/media-library-module',
      ]) . '</p>';
      $output .= '<h3>' . t('Selection dialog') . '</h3>';
      $output .= '<p>' . t('When selecting media for an entity reference field or a text editor, Media Library opens a modal dialog to help users easily find and select media. The modal dialog can toggle between a grid-style and table-style interface, and new media items can be uploaded directly into it.') . '</p>';
      $output .= '<p>' . t('Within the dialog, media items are divided up by type. If more than one media type can be selected by the user, the available types will be displayed as a set of vertical tabs. To users who have appropriate permissions, each media type may also present a short form allowing you to upload or create new media items of that type.') . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Grid-style vs. table-style interface') . '</dt>';
      $output .= '<dd>' . t('The Media Library module provides a new grid-style interface for the media administration page that displays media as thumbnails, with minimal textual information, allowing users to visually browse media in their site. The existing table-style interface is better suited to displaying additional information about media items, in addition to being more accessible to users with assistive technology.') . '</dd>';
      $output .= '<dt>' . t('Reusing media in entity reference fields') . '</dt>';
      $output .= '<dd>' . t('Any entity reference field that references media can use the media library. To enable, configure the form display for the field to use the "Media library" widget.') . '</dd>';
      $output .= '<dt>' . t('Embedding media in text content') . '</dt>';
      $output .= '<dd>' . t('To use the media library within CKEditor, you must add the "Insert from Media Library" button to the CKEditor toolbar, and enable the "Embed media" filter in the text format associated with the text editor.') . '</dd>';
      $output .= '</dl>';
      $output .= '<h3>' . t('Customize') . '</h3>';
      $output .= '<ul>';
      $output .= '<li>' . t('Both the table-style and grid-style interfaces are regular views and can be customized via the Views UI, including sorting and filtering. This is the case for both the administration page and the modal dialog.') . '</li>';
      $output .= '<li>' . t('In the grid-style interface, which fields are displayed (including which image style is used for images) can be customized by configuring the "Media library" view mode for each of your <a href=":media-types">media types</a>. The thumbnail images in the grid-style interface can be customized by configuring the "Media Library thumbnail (220×220)" image style.', [
        ':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
      ]) . '</li>';
      $output .= '<li>' . t('When adding new media items within the modal dialog, which fields are displayed can be customized by configuring the "Media library" form mode for each of your <a href=":media-types">media types</a>.', [
        ':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
      ]) . '</li>';
      $output .= '</ul>';
71
72
73
74
      return $output;
  }
}

75
76
77
78
79
80
81
82
/**
 * Implements hook_media_source_info_alter().
 */
function media_library_media_source_info_alter(array &$sources) {
  $sources['audio_file']['forms']['media_library_add'] = FileUploadForm::class;
  $sources['file']['forms']['media_library_add'] = FileUploadForm::class;
  $sources['image']['forms']['media_library_add'] = FileUploadForm::class;
  $sources['video_file']['forms']['media_library_add'] = FileUploadForm::class;
83
  $sources['oembed:video']['forms']['media_library_add'] = OEmbedForm::class;
84
85
}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
 * Implements hook_theme().
 */
function media_library_theme() {
  return [
    'media__media_library' => [
      'base hook' => 'media',
    ],
  ];
}

/**
 * Implements hook_views_post_render().
 */
function media_library_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
  if ($view->id() === 'media_library') {
    $output['#attached']['library'][] = 'media_library/view';
103
    if (strpos($view->current_display, 'widget') === 0) {
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
      try {
        $query = MediaLibraryState::fromRequest($view->getRequest())->all();
      }
      catch (InvalidArgumentException $e) {
        // MediaLibraryState::fromRequest() will throw an exception if the view
        // is being previewed, since not all required query parameters will be
        // present. In a preview, however, this can be omitted since we're
        // merely previewing.
        // @todo Use the views API for checking for the preview mode when it
        //   lands. https://www.drupal.org/project/drupal/issues/3060855
        if (empty($view->preview) && empty($view->live_preview)) {
          throw $e;
        }
      }

119
120
121
122
123
124
125
126
127
128
129
130
      // If the current query contains any parameters we use to contextually
      // filter the view, ensure they persist across AJAX rebuilds.
      // The ajax_path is shared for all AJAX views on the page, but our query
      // parameters are prefixed and should not interfere with any other views.
      // @todo Rework or remove this in https://www.drupal.org/node/2983451
      if (!empty($query)) {
        $ajax_path = &$output['#attached']['drupalSettings']['views']['ajax_path'];
        $parsed_url = UrlHelper::parse($ajax_path);
        $query = array_merge($query, $parsed_url['query']);
        $ajax_path = $parsed_url['path'] . '?' . UrlHelper::buildQuery($query);
      }
    }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  }
}

/**
 * Implements hook_preprocess_media().
 */
function media_library_preprocess_media(&$variables) {
  if ($variables['view_mode'] === 'media_library') {
    /** @var \Drupal\media\MediaInterface $media */
    $media = $variables['media'];
    $variables['#cache']['contexts'][] = 'user.permissions';
    $rel = $media->access('edit') ? 'edit-form' : 'canonical';
    $variables['url'] = $media->toUrl($rel, [
      'language' => $media->language(),
    ]);
    $variables['preview_attributes'] = new Attribute();
147
    $variables['preview_attributes']->addClass('media-library-item__preview', 'js-media-library-item-preview');
148
149
150
151
152
153
    $variables['metadata_attributes'] = new Attribute();
    $variables['metadata_attributes']->addClass('media-library-item__attributes');
    $variables['status'] = $media->isPublished();
  }
}

154
155
156
157
158
159
160
161
/**
 * Implements hook_preprocess_views_view_fields().
 */
function media_library_preprocess_views_view_fields(&$variables) {
  // Add classes to media rendered entity field so it can be targeted for
  // styling and JavaScript mouseover and click events.
  if ($variables['view']->id() === 'media_library' && isset($variables['fields']['rendered_entity'])) {
    if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
162
      $variables['fields']['rendered_entity']->wrapper_attributes->addClass('js-click-to-select-trigger media-library-item__click-to-select-trigger');
163
164
165
166
    }
  }
}

167
168
169
170
171
172
173
174
/**
 * Alter the bulk form to add a more accessible label.
 *
 * @param array $form
 *   An associative array containing the structure of the form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current state of the form.
 *
175
 * @todo Remove in https://www.drupal.org/node/2983454
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
 */
function media_library_form_views_form_media_library_page_alter(array &$form, FormStateInterface $form_state) {
  if (isset($form['media_bulk_form']) && isset($form['output'])) {
    /** @var \Drupal\views\ViewExecutable $view */
    $view = $form['output'][0]['#view'];
    foreach (Element::getVisibleChildren($form['media_bulk_form']) as $key) {
      if (isset($view->result[$key])) {
        $media = $view->field['media_bulk_form']->getEntity($view->result[$key]);
        $form['media_bulk_form'][$key]['#title'] = t('Select @label', [
          '@label' => $media->label(),
        ]);
      }
    }
  }
}

192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
 * Implements hook_form_alter().
 */
function media_library_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  $form_object = $form_state->getFormObject();
  if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
    $form['#attributes']['class'][] = 'media-library-views-form';
    if (isset($form['header'])) {
      $form['header']['#attributes']['class'][] = 'media-library-views-form__header';
      $form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
    }
  }

  // Add after build to fix media library views exposed filter's submit button.
206
  if ($form_id === 'views_exposed_form' && strpos($form['#id'], 'views-exposed-form-media-library-widget') === 0) {
207
208
    $form['#after_build'][] = '_media_library_views_form_media_library_after_build';
  }
209
210
211
212
213

  // Configures media_library displays when a type is submitted.
  if ($form_object instanceof MediaTypeForm) {
    $form['actions']['submit']['#submit'][] = '_media_library_media_type_form_submit';
  }
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
}

/**
 * After build callback for views form media library.
 */
function _media_library_views_form_media_library_after_build(array $form, FormStateInterface $form_state) {
  // Remove .form-actions from media library views exposed filter actions
  // and replace with .media-library-view--form-actions.
  //
  // This prevents the views exposed filter's 'Apply filter' submit button from
  // being moved into the dialog's buttons.
  // @see \Drupal\Core\Render\Element\Actions::processActions
  // @see Drupal.behaviors.dialog.prepareDialogButtons
  if (($key = array_search('form-actions', $form['actions']['#attributes']['class'])) !== FALSE) {
    unset($form['actions']['#attributes']['class'][$key]);
  }
  $form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
  return $form;
}

234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/**
 * Submit callback for media type form.
 */
function _media_library_media_type_form_submit(array &$form, FormStateInterface $form_state) {
  $form_object = $form_state->getFormObject();
  if ($form_object->getOperation() === 'add') {
    $type = $form_object->getEntity();
    $form_display_created = _media_library_configure_form_display($type);
    $view_display_created = _media_library_configure_view_display($type);
    if ($form_display_created || $view_display_created) {
      \Drupal::messenger()->addStatus(t('Media Library form and view displays have been created for the %type media type.', [
        '%type' => $type->label(),
      ]));
    }
  }
}

251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/**
 * Implements hook_field_ui_preconfigured_options_alter().
 */
function media_library_field_ui_preconfigured_options_alter(array &$options, $field_type) {
  // If the field is not an "entity_reference"-based field, bail out.
  $class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_type);
  if (!is_a($class, EntityReferenceItem::class, TRUE)) {
    return;
  }

  // Set the default field widget for media to be the Media library.
  if (!empty($options['media'])) {
    $options['media']['entity_form_display']['type'] = 'media_library_widget';
  }
}

267
268
269
270
271
272
273
274
275
276
277
278
279
280
/**
 * Implements hook_local_tasks_alter().
 *
 * Removes tasks for the Media library if the view display no longer exists.
 */
function media_library_local_tasks_alter(&$local_tasks) {
  /** @var \Symfony\Component\Routing\RouteCollection $route_collection */
  $route_collection = \Drupal::service('router')->getRouteCollection();
  foreach (['media_library.grid', 'media_library.table'] as $key) {
    if (isset($local_tasks[$key]) && !$route_collection->get($local_tasks[$key]['route_name'])) {
      unset($local_tasks[$key]);
    }
  }
}
281

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/**
 * Implements hook_ENTITY_TYPE_access().
 */
function media_library_image_style_access(EntityInterface $entity, $operation, AccountInterface $account) {
  // Prevent the fallback 'media_library' image style from being deleted.
  // @todo: Lock the image style instead of preventing delete access.
  //   https://www.drupal.org/project/drupal/issues/2247293
  if ($operation === 'delete' && $entity->id() === 'media_library') {
    return AccessResult::forbidden();
  }
}

/**
 * Ensures that the given media type has a media_library form display.
 *
 * @param \Drupal\media\MediaTypeInterface $type
 *   The media type to configure.
 *
 * @return bool
 *   Whether a form display has been created or not.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _media_library_configure_form_display(MediaTypeInterface $type) {
  $display = EntityFormDisplay::load('media.' . $type->id() . '.media_library');

  if ($display) {
    return FALSE;
  }

  $values = [
    'targetEntityType' => 'media',
    'bundle' => $type->id(),
    'mode' => 'media_library',
    'status' => TRUE,
  ];
  $display = EntityFormDisplay::create($values);
  // Remove all default components.
  foreach (array_keys($display->getComponents()) as $name) {
    $display->removeComponent($name);
  }
  // Expose the name field when it is not mapped.
  $field_map = $type->getFieldMap();
  if (empty($field_map['name'])) {
    $display->setComponent('name', [
      'type' => 'string_textfield',
      'settings' => [
        'size' => 60,
      ],
    ]);
  }
  // If the source field is an image field, expose it so that users can set alt
  // and title text.
  $source_field = $type->getSource()->getSourceFieldDefinition($type);
  if ($source_field->isDisplayConfigurable('form') && is_a($source_field->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
    $type->getSource()->prepareFormDisplay($type, $display);
  }
  return (bool) $display->save();
}

/**
 * Ensures that the given media type has a media_library view display.
 *
 * @param \Drupal\media\MediaTypeInterface $type
 *   The media type to configure.
 *
 * @return bool
 *   Whether a view display has been created or not.
 *
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function _media_library_configure_view_display(MediaTypeInterface $type) {
  $display = EntityViewDisplay::load('media.' . $type->id() . '.media_library');

  if ($display) {
    return FALSE;
  }

  $values = [
    'targetEntityType' => 'media',
    'bundle' => $type->id(),
    'mode' => 'media_library',
    'status' => TRUE,
  ];
  $display = EntityViewDisplay::create($values);
  // Remove all default components.
  foreach (array_keys($display->getComponents()) as $name) {
    $display->removeComponent($name);
  }

  // @todo: Remove dependency on 'medium' and 'thumbnail' image styles from
  //   media and media library modules.
  //   https://www.drupal.org/project/drupal/issues/3030437
  $image_style = ImageStyle::load('medium');

  // Expose the thumbnail component. If the medium image style doesn't exist,
  // use the fallback 'media_library' image style.
  $display->setComponent('thumbnail', [
    'type' => 'image',
    'label' => 'hidden',
    'settings' => [
      'image_style' => $image_style ? $image_style->id() : 'media_library',
      'image_link' => '',
    ],
  ]);
  return (bool) $display->save();
}
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458

/**
 * Implements hook_form_FORM_ID_alter().
 */
function media_library_form_filter_format_edit_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  // Add an additional validate callback so so we can ensure the media_embed
  // filter is enabled when the DrupalMediaLibrary button is enabled.
  $form['#validate'][] = 'media_library_filter_format_edit_form_validate';
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function media_library_form_filter_format_add_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  // Add an additional validate callback so so we can ensure the media_embed
  // filter is enabled when the DrupalMediaLibrary button is enabled.
  $form['#validate'][] = 'media_library_filter_format_edit_form_validate';
}

/**
 * Validate callback to ensure the DrupalMediaLibrary button can work correctly.
 */
function media_library_filter_format_edit_form_validate($form, FormStateInterface $form_state) {
  if ($form_state->getTriggeringElement()['#name'] !== 'op') {
    return;
  }

  // The "DrupalMediaLibrary" button is for the CKEditor text editor.
  if ($form_state->getValue(['editor', 'editor']) !== 'ckeditor') {
    return;
  }

  $button_group_path = [
    'editor',
    'settings',
    'toolbar',
    'button_groups',
  ];

  if ($button_groups = $form_state->getValue($button_group_path)) {
    $buttons = [];
    $button_groups = Json::decode($button_groups);

    foreach ($button_groups as $button_row) {
      foreach ($button_row as $button_group) {
        $buttons = array_merge($buttons, array_values($button_group['items']));
      }
    }

    $get_filter_label = function ($filter_plugin_id) use ($form) {
      return (string) $form['filters']['order'][$filter_plugin_id]['filter']['#markup'];
    };

    if (in_array('DrupalMediaLibrary', $buttons, TRUE)) {
      $media_embed_enabled = $form_state->getValue([
        'filters',
        'media_embed',
        'status',
      ]);

      if (!$media_embed_enabled) {
        $error_message = new TranslatableMarkup('The %media-embed-filter-label filter must be enabled to use the %drupal-media-library-button button.', [
          '%media-embed-filter-label' => $get_filter_label('media_embed'),
          '%drupal-media-library-button' => new TranslatableMarkup('Insert from Media Library'),
        ]);
        $form_state->setErrorByName('filters', $error_message);
      }
    }
  }
}