Chris@0: ' . t('About') . ''; Chris@14: $output .= '
' . 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 online documentation for the Media module.', [':media' => 'https://www.drupal.org/docs/8/core/modules/media']) . '
'; Chris@0: $output .= '' . t('Media reference fields offer several advantages over basic File and Image references:') . '
'; Chris@14: $output .= '' . t('Use Media reference fields for most files, images, audio, videos, and remote media. Use File or Image reference fields when creating your own media types, or for legacy files and images created before enabling the Media module.') . '
'; Chris@0: return $output; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_theme(). Chris@0: */ Chris@0: function media_theme() { Chris@0: return [ Chris@0: 'media' => [ Chris@0: 'render element' => 'elements', Chris@0: ], Chris@14: 'media_reference_help' => [ Chris@14: 'render element' => 'element', Chris@14: 'base hook' => 'field_multiple_value_form', Chris@14: ], Chris@17: 'media_oembed_iframe' => [ Chris@17: 'variables' => [ Chris@17: 'media' => NULL, Chris@18: 'placeholder_token' => '', Chris@17: ], Chris@17: ], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_entity_access(). Chris@0: */ Chris@0: function media_entity_access(EntityInterface $entity, $operation, AccountInterface $account) { Chris@0: if ($operation === 'delete' && $entity instanceof FieldConfigInterface && $entity->getTargetEntityTypeId() === 'media') { Chris@0: /** @var \Drupal\media\MediaTypeInterface $media_type */ Chris@0: $media_type = \Drupal::entityTypeManager()->getStorage('media_type')->load($entity->getTargetBundle()); Chris@0: return AccessResult::forbiddenIf($entity->id() === 'media.' . $media_type->id() . '.' . $media_type->getSource()->getConfiguration()['source_field']); Chris@0: } Chris@0: return AccessResult::neutral(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements hook_theme_suggestions_HOOK(). Chris@0: */ Chris@0: function media_theme_suggestions_media(array $variables) { Chris@0: $suggestions = []; Chris@17: /** @var \Drupal\media\MediaInterface $media */ Chris@0: $media = $variables['elements']['#media']; Chris@0: $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_'); Chris@0: Chris@0: $suggestions[] = 'media__' . $sanitized_view_mode; Chris@0: $suggestions[] = 'media__' . $media->bundle(); Chris@0: $suggestions[] = 'media__' . $media->bundle() . '__' . $sanitized_view_mode; Chris@0: Chris@17: // Add suggestions based on the source plugin ID. Chris@17: $source = $media->getSource(); Chris@17: if ($source instanceof DerivativeInspectionInterface) { Chris@17: $source_id = $source->getBaseId(); Chris@17: $derivative_id = $source->getDerivativeId(); Chris@17: if ($derivative_id) { Chris@17: $source_id .= '__derivative_' . $derivative_id; Chris@17: } Chris@17: } Chris@17: else { Chris@17: $source_id = $source->getPluginId(); Chris@17: } Chris@17: $suggestions[] = "media__source_$source_id"; Chris@17: Chris@17: // If the source plugin uses oEmbed, add a suggestion based on the provider Chris@17: // name, if available. Chris@17: if ($source instanceof OEmbedInterface) { Chris@17: $provider_id = $source->getMetadata($media, 'provider_name'); Chris@17: if ($provider_id) { Chris@17: $provider_id = \Drupal::transliteration()->transliterate($provider_id); Chris@17: $provider_id = preg_replace('/[^a-z0-9_]+/', '_', mb_strtolower($provider_id)); Chris@17: $suggestions[] = end($suggestions) . "__provider_$provider_id"; Chris@17: } Chris@17: } Chris@17: Chris@0: return $suggestions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Prepares variables for media templates. Chris@0: * Chris@0: * Default template: media.html.twig. Chris@0: * Chris@0: * @param array $variables Chris@0: * An associative array containing: Chris@0: * - elements: An array of elements to display in view mode. Chris@0: * - media: The media item. Chris@0: * - name: The label for the media item. Chris@0: * - view_mode: View mode; e.g., 'full', 'teaser', etc. Chris@0: */ Chris@0: function template_preprocess_media(array &$variables) { Chris@0: $variables['media'] = $variables['elements']['#media']; Chris@0: $variables['view_mode'] = $variables['elements']['#view_mode']; Chris@0: $variables['name'] = $variables['media']->label(); Chris@0: Chris@0: // Helpful $content variable for templates. Chris@0: foreach (Element::children($variables['elements']) as $key) { Chris@0: $variables['content'][$key] = $variables['elements'][$key]; Chris@0: } Chris@0: } Chris@14: Chris@14: /** Chris@14: * Implements hook_field_ui_preconfigured_options_alter(). Chris@14: */ Chris@14: function media_field_ui_preconfigured_options_alter(array &$options, $field_type) { Chris@14: // If the field is not an "entity_reference"-based field, bail out. Chris@14: /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */ Chris@14: $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); Chris@14: $class = $field_type_manager->getPluginClass($field_type); Chris@14: if (!is_a($class, 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem', TRUE)) { Chris@14: return; Chris@14: } Chris@14: Chris@14: // Set the default formatter for media in entity reference fields to be the Chris@14: // "Rendered entity" formatter. Chris@14: if (!empty($options['media'])) { Chris@14: $options['media']['entity_view_display']['type'] = 'entity_reference_entity_view'; Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * Implements hook_form_FORM_ID_alter(). Chris@14: */ Chris@14: function media_form_field_ui_field_storage_add_form_alter(&$form, FormStateInterface $form_state, $form_id) { Chris@14: // Provide some help text to aid users decide whether they need a Media, Chris@14: // File, or Image reference field. Chris@14: $description_text = t('Use Media reference fields for most files, images, audio, videos, and remote media. Use File or Image reference fields when creating your own media types, or for legacy files and images created before enabling the Media module.'); Chris@14: if (\Drupal::moduleHandler()->moduleExists('help')) { Chris@14: $description_text .= ' ' . t('For more information, see the Media help page.', [ Chris@14: '@help_url' => Url::fromRoute('help.page', ['name' => 'media'])->toString(), Chris@14: ]); Chris@14: } Chris@14: $form['add']['description_wrapper'] = [ Chris@14: '#type' => 'container', Chris@14: ]; Chris@14: $field_types = [ Chris@14: 'file', Chris@14: 'image', Chris@14: 'field_ui:entity_reference:media', Chris@14: ]; Chris@14: foreach ($field_types as $field_name) { Chris@14: $form['add']['description_wrapper']["description_{$field_name}"] = [ Chris@14: '#type' => 'item', Chris@14: '#markup' => $description_text, Chris@14: '#states' => [ Chris@14: 'visible' => [ Chris@14: ':input[name="new_storage_type"]' => ['value' => $field_name], Chris@14: ], Chris@14: ], Chris@14: ]; Chris@14: } Chris@14: $form['add']['new_storage_type']['#weight'] = 0; Chris@14: $form['add']['description_wrapper']['#weight'] = 1; Chris@14: } Chris@14: Chris@14: /** Chris@14: * Implements hook_field_widget_multivalue_form_alter(). Chris@14: */ Chris@14: function media_field_widget_multivalue_form_alter(array &$elements, FormStateInterface $form_state, array $context) { Chris@14: // Do not alter the default settings form. Chris@14: if ($context['default']) { Chris@14: return; Chris@14: } Chris@14: Chris@14: // Only act on entity reference fields that reference media. Chris@14: $field_type = $context['items']->getFieldDefinition()->getType(); Chris@14: $target_type = $context['items']->getFieldDefinition()->getFieldStorageDefinition()->getSetting('target_type'); Chris@14: if ($field_type !== 'entity_reference' || $target_type !== 'media') { Chris@14: return; Chris@14: } Chris@14: Chris@14: // Autocomplete widgets need different help text than options widgets. Chris@14: $widget_plugin_id = $context['widget']->getPluginId(); Chris@14: if (in_array($widget_plugin_id, ['entity_reference_autocomplete', 'entity_reference_autocomplete_tags'])) { Chris@14: $is_autocomplete = TRUE; Chris@14: } Chris@14: else { Chris@14: // @todo We can't yet properly alter non-autocomplete fields. Resolve this Chris@14: // in https://www.drupal.org/node/2943020 and remove this condition. Chris@14: return; Chris@14: } Chris@14: $elements['#media_help'] = []; Chris@14: Chris@14: // Retrieve the media bundle list and add information for the user based on Chris@14: // which bundles are available to be created or referenced. Chris@14: $settings = $context['items']->getFieldDefinition()->getSetting('handler_settings'); Chris@17: $allowed_bundles = !empty($settings['target_bundles']) ? $settings['target_bundles'] : []; Chris@17: $add_url = _media_get_add_url($allowed_bundles); Chris@17: if ($add_url) { Chris@14: $elements['#media_help']['#media_add_help'] = t('Create your media on the media add page (opens a new window), then add it by name to the field below.', [':add_page' => $add_url]); Chris@14: } Chris@14: Chris@14: $elements['#theme'] = 'media_reference_help'; Chris@14: // @todo template_preprocess_field_multiple_value_form() assumes this key Chris@14: // exists, but it does not exist in the case of a single widget that Chris@14: // accepts multiple values. This is for some reason necessary to use Chris@14: // our template for the entity_autocomplete_tags widget. Chris@14: // Research and resolve this in https://www.drupal.org/node/2943020. Chris@14: if (empty($elements['#cardinality_multiple'])) { Chris@14: $elements['#cardinality_multiple'] = NULL; Chris@14: } Chris@14: Chris@14: // Use the title set on the element if it exists, otherwise fall back to the Chris@14: // field label. Chris@14: $elements['#media_help']['#original_label'] = isset($elements['#title']) ? $elements['#title'] : $context['items']->getFieldDefinition()->getLabel(); Chris@14: Chris@14: // Customize the label for the field widget. Chris@14: // @todo Research a better approach https://www.drupal.org/node/2943024. Chris@14: $use_existing_label = t('Use existing media'); Chris@14: if (!empty($elements[0]['target_id']['#title'])) { Chris@14: $elements[0]['target_id']['#title'] = $use_existing_label; Chris@14: } Chris@14: if (!empty($elements['#title'])) { Chris@14: $elements['#title'] = $use_existing_label; Chris@14: } Chris@14: if (!empty($elements['target_id']['#title'])) { Chris@14: $elements['target_id']['#title'] = $use_existing_label; Chris@14: } Chris@14: Chris@14: // This help text is only relevant for autocomplete widgets. When the user Chris@14: // is presented with options, they don't need to type anything or know what Chris@14: // types of media are allowed. Chris@14: if ($is_autocomplete) { Chris@14: $elements['#media_help']['#media_list_help'] = t('Type part of the media name.'); Chris@14: Chris@14: $overview_url = Url::fromRoute('entity.media.collection'); Chris@14: if ($overview_url->access()) { Chris@14: $elements['#media_help']['#media_list_link'] = t('See the media list (opens a new window) to help locate media.', [':list_url' => $overview_url->toString()]); Chris@14: } Chris@17: $all_bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo('media'); Chris@17: $bundle_labels = array_map(function ($bundle) use ($all_bundles) { Chris@17: return $all_bundles[$bundle]['label']; Chris@17: }, $allowed_bundles); Chris@14: $elements['#media_help']['#allowed_types_help'] = t('Allowed media types: %types', ['%types' => implode(", ", $bundle_labels)]); Chris@14: } Chris@14: } Chris@14: Chris@14: /** Chris@14: * Implements hook_preprocess_HOOK() for media reference widgets. Chris@14: */ Chris@14: function media_preprocess_media_reference_help(&$variables) { Chris@14: // Most of these attribute checks are copied from Chris@14: // template_preprocess_fieldset(). Our template extends Chris@14: // field-multiple-value-form.html.twig to provide our help text, but also Chris@14: // groups the information within a semantic fieldset with a legend. So, we Chris@14: // incorporate parity for both. Chris@14: $element = $variables['element']; Chris@14: Element::setAttributes($element, ['id']); Chris@14: RenderElement::setAttributes($element); Chris@14: $variables['attributes'] = isset($element['#attributes']) ? $element['#attributes'] : []; Chris@14: $variables['legend_attributes'] = new Attribute(); Chris@14: $variables['header_attributes'] = new Attribute(); Chris@14: $variables['description']['attributes'] = new Attribute(); Chris@14: $variables['legend_span_attributes'] = new Attribute(); Chris@14: Chris@14: if (!empty($element['#media_help'])) { Chris@14: foreach ($element['#media_help'] as $key => $text) { Chris@14: $variables[substr($key, 1)] = $text; Chris@14: } Chris@14: } Chris@14: } Chris@17: Chris@17: /** Chris@17: * Returns the appropriate URL to add media for the current user. Chris@17: * Chris@17: * @todo Remove in https://www.drupal.org/project/drupal/issues/2938116 Chris@17: * Chris@17: * @param string[] $allowed_bundles Chris@17: * An array of bundles that should be checked for create access. Chris@17: * Chris@17: * @return bool|\Drupal\Core\Url Chris@17: * The URL to add media, or FALSE if the user cannot create any media. Chris@17: * Chris@17: * @internal Chris@17: * This function is internal and may be removed in a minor release. Chris@17: */ Chris@17: function _media_get_add_url($allowed_bundles) { Chris@17: $access_handler = \Drupal::entityTypeManager()->getAccessControlHandler('media'); Chris@17: $create_bundles = array_filter($allowed_bundles, [$access_handler, 'createAccess']); Chris@17: Chris@17: // Add a section about how to create media if the user has access to do so. Chris@17: if (count($create_bundles) === 1) { Chris@17: return Url::fromRoute('entity.media.add_form', ['media_type' => reset($create_bundles)])->toString(); Chris@17: } Chris@17: elseif (count($create_bundles) > 1) { Chris@17: return Url::fromRoute('entity.media.add_page')->toString(); Chris@17: } Chris@17: Chris@17: return FALSE; Chris@17: } Chris@18: Chris@18: /** Chris@18: * Implements hook_entity_type_alter(). Chris@18: */ Chris@18: function media_entity_type_alter(array &$entity_types) { Chris@18: if (\Drupal::config('media.settings')->get('standalone_url')) { Chris@18: /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */ Chris@18: $entity_type = $entity_types['media']; Chris@18: $entity_type->setLinkTemplate('canonical', '/media/{media}'); Chris@18: } Chris@18: }