media_library.module 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. /**
  3. * @file
  4. * Contains hook implementations for the media_library module.
  5. */
  6. use Drupal\Component\Utility\UrlHelper;
  7. use Drupal\Component\Serialization\Json;
  8. use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
  9. use Drupal\Core\Form\FormStateInterface;
  10. use Drupal\Core\Render\Element;
  11. use Drupal\Core\Routing\RouteMatchInterface;
  12. use Drupal\Core\Template\Attribute;
  13. use Drupal\Core\Url;
  14. use Drupal\views\Form\ViewsForm;
  15. use Drupal\views\Plugin\views\cache\CachePluginBase;
  16. use Drupal\views\Plugin\views\query\QueryPluginBase;
  17. use Drupal\views\Plugin\views\query\Sql;
  18. use Drupal\views\ViewExecutable;
  19. /**
  20. * Implements hook_help().
  21. *
  22. * @todo Update in https://www.drupal.org/project/drupal/issues/2964789
  23. */
  24. function media_library_help($route_name, RouteMatchInterface $route_match) {
  25. switch ($route_name) {
  26. case 'help.page.media_library':
  27. $output = '<h3>' . t('About') . '</h3>';
  28. $output .= '<p>' . t('The Media library module overrides the /admin/content/media view to provide a rich visual interface for performing administrative operations on media. For more information, see the <a href=":media">online documentation for the Media library module</a>.', [':media' => 'https://www.drupal.org/docs/8/core/modules/media']) . '</p>';
  29. return $output;
  30. }
  31. }
  32. /**
  33. * Implements hook_theme().
  34. */
  35. function media_library_theme() {
  36. return [
  37. 'media__media_library' => [
  38. 'base hook' => 'media',
  39. ],
  40. ];
  41. }
  42. /**
  43. * Implements hook_preprocess_view().
  44. *
  45. * Adds a link to add media above the view.
  46. */
  47. function media_library_preprocess_views_view(&$variables) {
  48. $view = $variables['view'];
  49. if ($view->id() === 'media_library' && $view->current_display === 'widget') {
  50. $url = Url::fromRoute('media_library.upload');
  51. if ($url->access()) {
  52. $url->setOption('query', \Drupal::request()->query->all());
  53. $variables['header']['add_media'] = [
  54. '#type' => 'link',
  55. '#title' => t('Add media'),
  56. '#url' => $url,
  57. '#attributes' => [
  58. 'class' => ['button', 'button-action', 'button--primary', 'use-ajax'],
  59. 'data-dialog-type' => 'modal',
  60. 'data-dialog-options' => Json::encode([
  61. 'dialogClass' => 'media-library-widget-modal',
  62. 'height' => '75%',
  63. 'width' => '75%',
  64. 'title' => t('Add media'),
  65. ]),
  66. ],
  67. ];
  68. }
  69. }
  70. }
  71. /**
  72. * Implements hook_views_post_render().
  73. */
  74. function media_library_views_post_render(ViewExecutable $view, &$output, CachePluginBase $cache) {
  75. if ($view->id() === 'media_library') {
  76. $output['#attached']['library'][] = 'media_library/view';
  77. if ($view->current_display === 'widget') {
  78. $query = array_intersect_key(\Drupal::request()->query->all(), array_flip([
  79. 'media_library_widget_id',
  80. 'media_library_allowed_types',
  81. 'media_library_remaining',
  82. ]));
  83. // If the current query contains any parameters we use to contextually
  84. // filter the view, ensure they persist across AJAX rebuilds.
  85. // The ajax_path is shared for all AJAX views on the page, but our query
  86. // parameters are prefixed and should not interfere with any other views.
  87. // @todo Rework or remove this in https://www.drupal.org/node/2983451
  88. if (!empty($query)) {
  89. $ajax_path = &$output['#attached']['drupalSettings']['views']['ajax_path'];
  90. $parsed_url = UrlHelper::parse($ajax_path);
  91. $query = array_merge($query, $parsed_url['query']);
  92. $ajax_path = $parsed_url['path'] . '?' . UrlHelper::buildQuery($query);
  93. if (isset($query['media_library_remaining'])) {
  94. $output['#attached']['drupalSettings']['media_library']['selection_remaining'] = (int) $query['media_library_remaining'];
  95. }
  96. }
  97. }
  98. }
  99. }
  100. /**
  101. * Implements hook_preprocess_media().
  102. */
  103. function media_library_preprocess_media(&$variables) {
  104. if ($variables['view_mode'] === 'media_library') {
  105. /** @var \Drupal\media\MediaInterface $media */
  106. $media = $variables['media'];
  107. $variables['#cache']['contexts'][] = 'user.permissions';
  108. $rel = $media->access('edit') ? 'edit-form' : 'canonical';
  109. $variables['url'] = $media->toUrl($rel, [
  110. 'language' => $media->language(),
  111. ]);
  112. $variables['preview_attributes'] = new Attribute();
  113. $variables['preview_attributes']->addClass('media-library-item__preview', 'js-media-library-item-preview', 'js-click-to-select-trigger');
  114. $variables['metadata_attributes'] = new Attribute();
  115. $variables['metadata_attributes']->addClass('media-library-item__attributes');
  116. $variables['status'] = $media->isPublished();
  117. }
  118. }
  119. /**
  120. * Alter the bulk form to add a more accessible label.
  121. *
  122. * @param array $form
  123. * An associative array containing the structure of the form.
  124. * @param \Drupal\Core\Form\FormStateInterface $form_state
  125. * The current state of the form.
  126. *
  127. * @todo Remove in https://www.drupal.org/node/2983454
  128. */
  129. function media_library_form_views_form_media_library_page_alter(array &$form, FormStateInterface $form_state) {
  130. if (isset($form['media_bulk_form']) && isset($form['output'])) {
  131. /** @var \Drupal\views\ViewExecutable $view */
  132. $view = $form['output'][0]['#view'];
  133. foreach (Element::getVisibleChildren($form['media_bulk_form']) as $key) {
  134. if (isset($view->result[$key])) {
  135. $media = $view->field['media_bulk_form']->getEntity($view->result[$key]);
  136. $form['media_bulk_form'][$key]['#title'] = t('Select @label', [
  137. '@label' => $media->label(),
  138. ]);
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Implements hook_form_alter().
  145. */
  146. function media_library_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  147. $form_object = $form_state->getFormObject();
  148. if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
  149. $form['#attributes']['class'][] = 'media-library-views-form';
  150. if (isset($form['header'])) {
  151. $form['header']['#attributes']['class'][] = 'media-library-views-form__header';
  152. $form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
  153. }
  154. }
  155. // Add after build to fix media library views exposed filter's submit button.
  156. if ($form_id === 'views_exposed_form' && $form['#id'] === 'views-exposed-form-media-library-widget') {
  157. $form['#after_build'][] = '_media_library_views_form_media_library_after_build';
  158. }
  159. }
  160. /**
  161. * After build callback for views form media library.
  162. */
  163. function _media_library_views_form_media_library_after_build(array $form, FormStateInterface $form_state) {
  164. // Remove .form-actions from media library views exposed filter actions
  165. // and replace with .media-library-view--form-actions.
  166. //
  167. // This prevents the views exposed filter's 'Apply filter' submit button from
  168. // being moved into the dialog's buttons.
  169. // @see \Drupal\Core\Render\Element\Actions::processActions
  170. // @see Drupal.behaviors.dialog.prepareDialogButtons
  171. if (($key = array_search('form-actions', $form['actions']['#attributes']['class'])) !== FALSE) {
  172. unset($form['actions']['#attributes']['class'][$key]);
  173. }
  174. $form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
  175. return $form;
  176. }
  177. /**
  178. * Implements hook_views_query_alter().
  179. *
  180. * Alters the widget view's query to only show media that can be selected,
  181. * based on what types are allowed in the field settings.
  182. *
  183. * @todo Remove in https://www.drupal.org/node/2983454
  184. */
  185. function media_library_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
  186. if ($query instanceof Sql && $view->id() === 'media_library' && $view->current_display === 'widget') {
  187. $types = _media_library_get_allowed_types();
  188. if ($types) {
  189. $entity_type = \Drupal::entityTypeManager()->getDefinition('media');
  190. $group = $query->setWhereGroup();
  191. $query->addWhere($group, $entity_type->getDataTable() . '.' . $entity_type->getKey('bundle'), $types, 'in');
  192. }
  193. }
  194. }
  195. /**
  196. * Implements hook_form_FORM_ID_alter().
  197. *
  198. * Limits the types available in the exposed filter to avoid users trying to
  199. * filter by a type that is un-selectable.
  200. *
  201. * @see media_library_views_query_alter()
  202. *
  203. * @todo Remove in https://www.drupal.org/node/2983454
  204. */
  205. function media_library_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state) {
  206. if (isset($form['#id']) && $form['#id'] === 'views-exposed-form-media-library-widget') {
  207. $types = _media_library_get_allowed_types();
  208. if ($types && isset($form['type']['#options'])) {
  209. $keys = array_flip($types);
  210. // Ensure that the default value (by default "All") persists.
  211. if (isset($form['type']['#default_value'])) {
  212. $keys[$form['type']['#default_value']] = TRUE;
  213. }
  214. $form['type']['#options'] = array_intersect_key($form['type']['#options'], $keys);
  215. }
  216. }
  217. }
  218. /**
  219. * Implements hook_field_ui_preconfigured_options_alter().
  220. */
  221. function media_library_field_ui_preconfigured_options_alter(array &$options, $field_type) {
  222. // If the field is not an "entity_reference"-based field, bail out.
  223. $class = \Drupal::service('plugin.manager.field.field_type')->getPluginClass($field_type);
  224. if (!is_a($class, EntityReferenceItem::class, TRUE)) {
  225. return;
  226. }
  227. // Set the default field widget for media to be the Media library.
  228. if (!empty($options['media'])) {
  229. $options['media']['entity_form_display']['type'] = 'media_library_widget';
  230. }
  231. }
  232. /**
  233. * Implements hook_local_tasks_alter().
  234. *
  235. * Removes tasks for the Media library if the view display no longer exists.
  236. */
  237. function media_library_local_tasks_alter(&$local_tasks) {
  238. /** @var \Symfony\Component\Routing\RouteCollection $route_collection */
  239. $route_collection = \Drupal::service('router')->getRouteCollection();
  240. foreach (['media_library.grid', 'media_library.table'] as $key) {
  241. if (isset($local_tasks[$key]) && !$route_collection->get($local_tasks[$key]['route_name'])) {
  242. unset($local_tasks[$key]);
  243. }
  244. }
  245. }
  246. /**
  247. * Determines what types are allowed based on the current request.
  248. *
  249. * @return array
  250. * An array of allowed types.
  251. */
  252. function _media_library_get_allowed_types() {
  253. $types = \Drupal::request()->query->get('media_library_allowed_types');
  254. if ($types && is_array($types)) {
  255. return array_filter($types, 'is_string');
  256. }
  257. return [];
  258. }