annotate core/modules/media/media.module @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /**
Chris@0 4 * @file
Chris@0 5 * Provides media items.
Chris@0 6 */
Chris@0 7
Chris@17 8 use Drupal\Component\Plugin\DerivativeInspectionInterface;
Chris@0 9 use Drupal\Core\Access\AccessResult;
Chris@14 10 use Drupal\Core\Entity\EntityInterface;
Chris@14 11 use Drupal\Core\Form\FormStateInterface;
Chris@14 12 use Drupal\Core\Render\Element;
Chris@14 13 use Drupal\Core\Render\Element\RenderElement;
Chris@14 14 use Drupal\Core\Routing\RouteMatchInterface;
Chris@0 15 use Drupal\Core\Session\AccountInterface;
Chris@14 16 use Drupal\Core\Template\Attribute;
Chris@14 17 use Drupal\Core\Url;
Chris@0 18 use Drupal\field\FieldConfigInterface;
Chris@17 19 use Drupal\media\Plugin\media\Source\OEmbedInterface;
Chris@0 20
Chris@0 21 /**
Chris@0 22 * Implements hook_help().
Chris@0 23 */
Chris@0 24 function media_help($route_name, RouteMatchInterface $route_match) {
Chris@0 25 switch ($route_name) {
Chris@0 26 case 'help.page.media':
Chris@0 27 $output = '<h3>' . t('About') . '</h3>';
Chris@14 28 $output .= '<p>' . t('The Media module manages the creation, editing, deletion, settings, and display of media. Items are typically images, documents, slideshows, YouTube videos, tweets, Instagram photos, etc. You can reference media items from any other content on your site. For more information, see the <a href=":media">online documentation for the Media module</a>.', [':media' => 'https://www.drupal.org/docs/8/core/modules/media']) . '</p>';
Chris@0 29 $output .= '<h3>' . t('Uses') . '</h3>';
Chris@0 30 $output .= '<dl>';
Chris@0 31 $output .= '<dt>' . t('Creating media items') . '</dt>';
Chris@0 32 $output .= '<dd>' . t('When a new media item is created, the Media module records basic information about it, including the author, date of creation, and the <a href=":media-type">media type</a>. It also manages the <em>publishing options</em>, which define whether or not the item is published. Default settings can be configured for each type of media on your site.', [':media-type' => Url::fromRoute('entity.media_type.collection')->toString()]) . '</dd>';
Chris@14 33 $output .= '<dt>' . t('Listing media items') . '</dt>';
Chris@14 34 $output .= '<dd>' . t('Media items are listed at the <a href=":media-collection">media administration page</a>.', [
Chris@14 35 ':media-collection' => Url::fromRoute('entity.media.collection')->toString(),
Chris@14 36 ]) . '</dd>';
Chris@0 37 $output .= '<dt>' . t('Creating custom media types') . '</dt>';
Chris@14 38 $output .= '<dd>' . t('The Media module gives users with the <em>Administer media types</em> permission the ability to <a href=":media-new">create new media types</a> in addition to the default ones already configured. Each media type has an associated media source (such as the image source) which support thumbnail generation and metadata extraction. Fields managed by the <a href=":field">Field module</a> may be added for storing that metadata, such as width and height, as well as any other associated values.', [
Chris@14 39 ':media-new' => Url::fromRoute('entity.media_type.add_form')->toString(),
Chris@14 40 ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString(),
Chris@14 41 ]) . '</dd>';
Chris@0 42 $output .= '<dt>' . t('Creating revisions') . '</dt>';
Chris@0 43 $output .= '<dd>' . t('The Media module also enables you to create multiple versions of any media item, and revert to older versions using the <em>Revision information</em> settings.') . '</dd>';
Chris@0 44 $output .= '<dt>' . t('User permissions') . '</dt>';
Chris@14 45 $output .= '<dd>' . t('The Media module makes a number of permissions available, which can be set by role on the <a href=":permissions">permissions page</a>.', [
Chris@14 46 ':permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-media'])->toString(),
Chris@14 47 ]) . '</dd>';
Chris@14 48 $output .= '<dt>' . t('Adding media to other content') . '</dt>';
Chris@14 49 $output .= '<dd>' . t('Users with permission to administer content types can add media support by adding a media reference field to the content type on the content type administration page. (The same is true of block types, taxonomy terms, user profiles, and other content that supports fields.) A media reference field can refer to any configured media type. It is possible to allow multiple media types in the same field.') . '</dd>';
Chris@0 50 $output .= '</dl>';
Chris@14 51 $output .= '<h3>' . t('Differences between Media, File, and Image reference fields') . '</h3>';
Chris@14 52 $output .= '<p>' . t('<em>Media</em> reference fields offer several advantages over basic <em>File</em> and <em>Image</em> references:') . '</p>';
Chris@14 53 $output .= '<ul>';
Chris@14 54 $output .= '<li>' . t('Media reference fields can reference multiple media types in the same field.') . '</li>';
Chris@14 55 $output .= '<li>' . t('Fields can also be added to media types themselves, which means that custom metadata like descriptions and taxonomy tags can be added for the referenced media. (Basic file and image fields do not support this.)') . '</li>';
Chris@14 56 $output .= '<li>' . t('Media types for audio and video files are provided by default, so there is no need for additional configuration to upload these media.') . '</li>';
Chris@14 57 $output .= '<li>' . t('Contributed or custom projects can provide additional media sources (such as third-party websites, Twitter, etc.).') . '</li>';
Chris@14 58 $output .= '<li>' . t('Existing media items can be reused on any other content items with a media reference field.') . '</li>';
Chris@14 59 $output .= '</ul>';
Chris@14 60 $output .= '<p>' . t('Use <em>Media</em> reference fields for most files, images, audio, videos, and remote media. Use <em>File</em> or <em>Image</em> reference fields when creating your own media types, or for legacy files and images created before enabling the Media module.') . '</p>';
Chris@0 61 return $output;
Chris@0 62 }
Chris@0 63 }
Chris@0 64
Chris@0 65 /**
Chris@0 66 * Implements hook_theme().
Chris@0 67 */
Chris@0 68 function media_theme() {
Chris@0 69 return [
Chris@0 70 'media' => [
Chris@0 71 'render element' => 'elements',
Chris@0 72 ],
Chris@14 73 'media_reference_help' => [
Chris@14 74 'render element' => 'element',
Chris@14 75 'base hook' => 'field_multiple_value_form',
Chris@14 76 ],
Chris@17 77 'media_oembed_iframe' => [
Chris@17 78 'variables' => [
Chris@17 79 'media' => NULL,
Chris@18 80 'placeholder_token' => '',
Chris@17 81 ],
Chris@17 82 ],
Chris@0 83 ];
Chris@0 84 }
Chris@0 85
Chris@0 86 /**
Chris@0 87 * Implements hook_entity_access().
Chris@0 88 */
Chris@0 89 function media_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
Chris@0 90 if ($operation === 'delete' && $entity instanceof FieldConfigInterface && $entity->getTargetEntityTypeId() === 'media') {
Chris@0 91 /** @var \Drupal\media\MediaTypeInterface $media_type */
Chris@0 92 $media_type = \Drupal::entityTypeManager()->getStorage('media_type')->load($entity->getTargetBundle());
Chris@0 93 return AccessResult::forbiddenIf($entity->id() === 'media.' . $media_type->id() . '.' . $media_type->getSource()->getConfiguration()['source_field']);
Chris@0 94 }
Chris@0 95 return AccessResult::neutral();
Chris@0 96 }
Chris@0 97
Chris@0 98 /**
Chris@0 99 * Implements hook_theme_suggestions_HOOK().
Chris@0 100 */
Chris@0 101 function media_theme_suggestions_media(array $variables) {
Chris@0 102 $suggestions = [];
Chris@17 103 /** @var \Drupal\media\MediaInterface $media */
Chris@0 104 $media = $variables['elements']['#media'];
Chris@0 105 $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
Chris@0 106
Chris@0 107 $suggestions[] = 'media__' . $sanitized_view_mode;
Chris@0 108 $suggestions[] = 'media__' . $media->bundle();
Chris@0 109 $suggestions[] = 'media__' . $media->bundle() . '__' . $sanitized_view_mode;
Chris@0 110
Chris@17 111 // Add suggestions based on the source plugin ID.
Chris@17 112 $source = $media->getSource();
Chris@17 113 if ($source instanceof DerivativeInspectionInterface) {
Chris@17 114 $source_id = $source->getBaseId();
Chris@17 115 $derivative_id = $source->getDerivativeId();
Chris@17 116 if ($derivative_id) {
Chris@17 117 $source_id .= '__derivative_' . $derivative_id;
Chris@17 118 }
Chris@17 119 }
Chris@17 120 else {
Chris@17 121 $source_id = $source->getPluginId();
Chris@17 122 }
Chris@17 123 $suggestions[] = "media__source_$source_id";
Chris@17 124
Chris@17 125 // If the source plugin uses oEmbed, add a suggestion based on the provider
Chris@17 126 // name, if available.
Chris@17 127 if ($source instanceof OEmbedInterface) {
Chris@17 128 $provider_id = $source->getMetadata($media, 'provider_name');
Chris@17 129 if ($provider_id) {
Chris@17 130 $provider_id = \Drupal::transliteration()->transliterate($provider_id);
Chris@17 131 $provider_id = preg_replace('/[^a-z0-9_]+/', '_', mb_strtolower($provider_id));
Chris@17 132 $suggestions[] = end($suggestions) . "__provider_$provider_id";
Chris@17 133 }
Chris@17 134 }
Chris@17 135
Chris@0 136 return $suggestions;
Chris@0 137 }
Chris@0 138
Chris@0 139 /**
Chris@0 140 * Prepares variables for media templates.
Chris@0 141 *
Chris@0 142 * Default template: media.html.twig.
Chris@0 143 *
Chris@0 144 * @param array $variables
Chris@0 145 * An associative array containing:
Chris@0 146 * - elements: An array of elements to display in view mode.
Chris@0 147 * - media: The media item.
Chris@0 148 * - name: The label for the media item.
Chris@0 149 * - view_mode: View mode; e.g., 'full', 'teaser', etc.
Chris@0 150 */
Chris@0 151 function template_preprocess_media(array &$variables) {
Chris@0 152 $variables['media'] = $variables['elements']['#media'];
Chris@0 153 $variables['view_mode'] = $variables['elements']['#view_mode'];
Chris@0 154 $variables['name'] = $variables['media']->label();
Chris@0 155
Chris@0 156 // Helpful $content variable for templates.
Chris@0 157 foreach (Element::children($variables['elements']) as $key) {
Chris@0 158 $variables['content'][$key] = $variables['elements'][$key];
Chris@0 159 }
Chris@0 160 }
Chris@14 161
Chris@14 162 /**
Chris@14 163 * Implements hook_field_ui_preconfigured_options_alter().
Chris@14 164 */
Chris@14 165 function media_field_ui_preconfigured_options_alter(array &$options, $field_type) {
Chris@14 166 // If the field is not an "entity_reference"-based field, bail out.
Chris@14 167 /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
Chris@14 168 $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
Chris@14 169 $class = $field_type_manager->getPluginClass($field_type);
Chris@14 170 if (!is_a($class, 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', TRUE)) {
Chris@14 171 return;
Chris@14 172 }
Chris@14 173
Chris@14 174 // Set the default formatter for media in entity reference fields to be the
Chris@14 175 // "Rendered entity" formatter.
Chris@14 176 if (!empty($options['media'])) {
Chris@14 177 $options['media']['entity_view_display']['type'] = 'entity_reference_entity_view';
Chris@14 178 }
Chris@14 179 }
Chris@14 180
Chris@14 181 /**
Chris@14 182 * Implements hook_form_FORM_ID_alter().
Chris@14 183 */
Chris@14 184 function media_form_field_ui_field_storage_add_form_alter(&$form, FormStateInterface $form_state, $form_id) {
Chris@14 185 // Provide some help text to aid users decide whether they need a Media,
Chris@14 186 // File, or Image reference field.
Chris@14 187 $description_text = t('Use <em>Media</em> reference fields for most files, images, audio, videos, and remote media. Use <em>File</em> or <em>Image</em> reference fields when creating your own media types, or for legacy files and images created before enabling the Media module.');
Chris@14 188 if (\Drupal::moduleHandler()->moduleExists('help')) {
Chris@14 189 $description_text .= ' ' . t('For more information, see the <a href="@help_url">Media help page</a>.', [
Chris@14 190 '@help_url' => Url::fromRoute('help.page', ['name' => 'media'])->toString(),
Chris@14 191 ]);
Chris@14 192 }
Chris@14 193 $form['add']['description_wrapper'] = [
Chris@14 194 '#type' => 'container',
Chris@14 195 ];
Chris@14 196 $field_types = [
Chris@14 197 'file',
Chris@14 198 'image',
Chris@14 199 'field_ui:entity_reference:media',
Chris@14 200 ];
Chris@14 201 foreach ($field_types as $field_name) {
Chris@14 202 $form['add']['description_wrapper']["description_{$field_name}"] = [
Chris@14 203 '#type' => 'item',
Chris@14 204 '#markup' => $description_text,
Chris@14 205 '#states' => [
Chris@14 206 'visible' => [
Chris@14 207 ':input[name="new_storage_type"]' => ['value' => $field_name],
Chris@14 208 ],
Chris@14 209 ],
Chris@14 210 ];
Chris@14 211 }
Chris@14 212 $form['add']['new_storage_type']['#weight'] = 0;
Chris@14 213 $form['add']['description_wrapper']['#weight'] = 1;
Chris@14 214 }
Chris@14 215
Chris@14 216 /**
Chris@14 217 * Implements hook_field_widget_multivalue_form_alter().
Chris@14 218 */
Chris@14 219 function media_field_widget_multivalue_form_alter(array &$elements, FormStateInterface $form_state, array $context) {
Chris@14 220 // Do not alter the default settings form.
Chris@14 221 if ($context['default']) {
Chris@14 222 return;
Chris@14 223 }
Chris@14 224
Chris@14 225 // Only act on entity reference fields that reference media.
Chris@14 226 $field_type = $context['items']->getFieldDefinition()->getType();
Chris@14 227 $target_type = $context['items']->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type');
Chris@14 228 if ($field_type !== 'entity_reference' || $target_type !== 'media') {
Chris@14 229 return;
Chris@14 230 }
Chris@14 231
Chris@14 232 // Autocomplete widgets need different help text than options widgets.
Chris@14 233 $widget_plugin_id = $context['widget']->getPluginId();
Chris@14 234 if (in_array($widget_plugin_id, ['entity_reference_autocomplete', 'entity_reference_autocomplete_tags'])) {
Chris@14 235 $is_autocomplete = TRUE;
Chris@14 236 }
Chris@14 237 else {
Chris@14 238 // @todo We can't yet properly alter non-autocomplete fields. Resolve this
Chris@14 239 // in https://www.drupal.org/node/2943020 and remove this condition.
Chris@14 240 return;
Chris@14 241 }
Chris@14 242 $elements['#media_help'] = [];
Chris@14 243
Chris@14 244 // Retrieve the media bundle list and add information for the user based on
Chris@14 245 // which bundles are available to be created or referenced.
Chris@14 246 $settings = $context['items']->getFieldDefinition()->getSetting('handler_settings');
Chris@17 247 $allowed_bundles = !empty($settings['target_bundles']) ? $settings['target_bundles'] : [];
Chris@17 248 $add_url = _media_get_add_url($allowed_bundles);
Chris@17 249 if ($add_url) {
Chris@14 250 $elements['#media_help']['#media_add_help'] = t('Create your media on the <a href=":add_page" target="_blank">media add page</a> (opens a new window), then add it by name to the field below.', [':add_page' => $add_url]);
Chris@14 251 }
Chris@14 252
Chris@14 253 $elements['#theme'] = 'media_reference_help';
Chris@14 254 // @todo template_preprocess_field_multiple_value_form() assumes this key
Chris@14 255 // exists, but it does not exist in the case of a single widget that
Chris@14 256 // accepts multiple values. This is for some reason necessary to use
Chris@14 257 // our template for the entity_autocomplete_tags widget.
Chris@14 258 // Research and resolve this in https://www.drupal.org/node/2943020.
Chris@14 259 if (empty($elements['#cardinality_multiple'])) {
Chris@14 260 $elements['#cardinality_multiple'] = NULL;
Chris@14 261 }
Chris@14 262
Chris@14 263 // Use the title set on the element if it exists, otherwise fall back to the
Chris@14 264 // field label.
Chris@14 265 $elements['#media_help']['#original_label'] = isset($elements['#title']) ? $elements['#title'] : $context['items']->getFieldDefinition()->getLabel();
Chris@14 266
Chris@14 267 // Customize the label for the field widget.
Chris@14 268 // @todo Research a better approach https://www.drupal.org/node/2943024.
Chris@14 269 $use_existing_label = t('Use existing media');
Chris@14 270 if (!empty($elements[0]['target_id']['#title'])) {
Chris@14 271 $elements[0]['target_id']['#title'] = $use_existing_label;
Chris@14 272 }
Chris@14 273 if (!empty($elements['#title'])) {
Chris@14 274 $elements['#title'] = $use_existing_label;
Chris@14 275 }
Chris@14 276 if (!empty($elements['target_id']['#title'])) {
Chris@14 277 $elements['target_id']['#title'] = $use_existing_label;
Chris@14 278 }
Chris@14 279
Chris@14 280 // This help text is only relevant for autocomplete widgets. When the user
Chris@14 281 // is presented with options, they don't need to type anything or know what
Chris@14 282 // types of media are allowed.
Chris@14 283 if ($is_autocomplete) {
Chris@14 284 $elements['#media_help']['#media_list_help'] = t('Type part of the media name.');
Chris@14 285
Chris@14 286 $overview_url = Url::fromRoute('entity.media.collection');
Chris@14 287 if ($overview_url->access()) {
Chris@14 288 $elements['#media_help']['#media_list_link'] = t('See the <a href=":list_url" target="_blank">media list</a> (opens a new window) to help locate media.', [':list_url' => $overview_url->toString()]);
Chris@14 289 }
Chris@17 290 $all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media');
Chris@17 291 $bundle_labels = array_map(function ($bundle) use ($all_bundles) {
Chris@17 292 return $all_bundles[$bundle]['label'];
Chris@17 293 }, $allowed_bundles);
Chris@14 294 $elements['#media_help']['#allowed_types_help'] = t('Allowed media types: %types', ['%types' => implode(", ", $bundle_labels)]);
Chris@14 295 }
Chris@14 296 }
Chris@14 297
Chris@14 298 /**
Chris@14 299 * Implements hook_preprocess_HOOK() for media reference widgets.
Chris@14 300 */
Chris@14 301 function media_preprocess_media_reference_help(&$variables) {
Chris@14 302 // Most of these attribute checks are copied from
Chris@14 303 // template_preprocess_fieldset(). Our template extends
Chris@14 304 // field-multiple-value-form.html.twig to provide our help text, but also
Chris@14 305 // groups the information within a semantic fieldset with a legend. So, we
Chris@14 306 // incorporate parity for both.
Chris@14 307 $element = $variables['element'];
Chris@14 308 Element::setAttributes($element, ['id']);
Chris@14 309 RenderElement::setAttributes($element);
Chris@14 310 $variables['attributes'] = isset($element['#attributes']) ? $element['#attributes'] : [];
Chris@14 311 $variables['legend_attributes'] = new Attribute();
Chris@14 312 $variables['header_attributes'] = new Attribute();
Chris@14 313 $variables['description']['attributes'] = new Attribute();
Chris@14 314 $variables['legend_span_attributes'] = new Attribute();
Chris@14 315
Chris@14 316 if (!empty($element['#media_help'])) {
Chris@14 317 foreach ($element['#media_help'] as $key => $text) {
Chris@14 318 $variables[substr($key, 1)] = $text;
Chris@14 319 }
Chris@14 320 }
Chris@14 321 }
Chris@17 322
Chris@17 323 /**
Chris@17 324 * Returns the appropriate URL to add media for the current user.
Chris@17 325 *
Chris@17 326 * @todo Remove in https://www.drupal.org/project/drupal/issues/2938116
Chris@17 327 *
Chris@17 328 * @param string[] $allowed_bundles
Chris@17 329 * An array of bundles that should be checked for create access.
Chris@17 330 *
Chris@17 331 * @return bool|\Drupal\Core\Url
Chris@17 332 * The URL to add media, or FALSE if the user cannot create any media.
Chris@17 333 *
Chris@17 334 * @internal
Chris@17 335 * This function is internal and may be removed in a minor release.
Chris@17 336 */
Chris@17 337 function _media_get_add_url($allowed_bundles) {
Chris@17 338 $access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media');
Chris@17 339 $create_bundles = array_filter($allowed_bundles, [$access_handler, 'createAccess']);
Chris@17 340
Chris@17 341 // Add a section about how to create media if the user has access to do so.
Chris@17 342 if (count($create_bundles) === 1) {
Chris@17 343 return Url::fromRoute('entity.media.add_form', ['media_type' => reset($create_bundles)])->toString();
Chris@17 344 }
Chris@17 345 elseif (count($create_bundles) > 1) {
Chris@17 346 return Url::fromRoute('entity.media.add_page')->toString();
Chris@17 347 }
Chris@17 348
Chris@17 349 return FALSE;
Chris@17 350 }
Chris@18 351
Chris@18 352 /**
Chris@18 353 * Implements hook_entity_type_alter().
Chris@18 354 */
Chris@18 355 function media_entity_type_alter(array &$entity_types) {
Chris@18 356 if (\Drupal::config('media.settings')->get('standalone_url')) {
Chris@18 357 /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
Chris@18 358 $entity_type = $entity_types['media'];
Chris@18 359 $entity_type->setLinkTemplate('canonical', '/media/{media}');
Chris@18 360 }
Chris@18 361 }