You are here

class EditorMediaDialog in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/media/src/Form/EditorMediaDialog.php \Drupal\media\Form\EditorMediaDialog

Provides a media embed dialog for text editors.

Depending on the configuration of the filters associated with the text editor, this dialog allows users to set the alt text, alignment, and captioning status for embedded media items.

@internal This is an internal part of the media system in Drupal core and may be subject to change in minor releases. This class should not be instantiated or extended by external code.

Hierarchy

Expanded class hierarchy of EditorMediaDialog

1 file declares its use of EditorMediaDialog
EditorMediaDialogTest.php in core/modules/media/tests/src/Kernel/EditorMediaDialogTest.php
1 string reference to 'EditorMediaDialog'
media.routing.yml in core/modules/media/media.routing.yml
core/modules/media/media.routing.yml

File

core/modules/media/src/Form/EditorMediaDialog.php, line 31

Namespace

Drupal\media\Form
View source
class EditorMediaDialog extends FormBase {

  /**
   * The entity repository.
   *
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
   */
  protected $entityRepository;

  /**
   * The entity display repository.
   *
   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
   */
  protected $entityDisplayRepository;

  /**
   * Constructs a EditorMediaDialog object.
   *
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository.
   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
   *   The entity display repository.
   */
  public function __construct(EntityRepositoryInterface $entity_repository, EntityDisplayRepositoryInterface $entity_display_repository) {
    $this->entityRepository = $entity_repository;
    $this->entityDisplayRepository = $entity_display_repository;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('entity.repository'), $container
      ->get('entity_display.repository'));
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'editor_media_dialog';
  }

  /**
   * {@inheritdoc}
   *
   * @param array $form
   *   A nested array form elements comprising the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   * @param \Drupal\editor\EditorInterface $editor
   *   The text editor to which this dialog corresponds.
   */
  public function buildForm(array $form, FormStateInterface $form_state, EditorInterface $editor = NULL) {

    // This form is special, in that the default values do not come from the
    // server side, but from the client side, from a text editor. We must cache
    // this data in form state, because when the form is rebuilt, we will be
    // receiving values from the form, instead of the values from the text
    // editor. If we don't cache it, this data will be lost. By convention,
    // the data that the text editor sends to any dialog is in the
    // 'editor_object' key.
    if (isset($form_state
      ->getUserInput()['editor_object'])) {
      $editor_object = $form_state
        ->getUserInput()['editor_object'];

      // The data that the text editor sends to any dialog is in
      // the 'editor_object' key.
      // @see core/modules/ckeditor/js/ckeditor.es6.js
      $media_embed_element = $editor_object['attributes'];
      $form_state
        ->set('media_embed_element', $media_embed_element);
      $has_caption = $editor_object['hasCaption'];
      $form_state
        ->set('hasCaption', $has_caption)
        ->setCached(TRUE);
    }
    else {

      // Retrieve the user input from form state.
      $media_embed_element = $form_state
        ->get('media_embed_element');
      $has_caption = $form_state
        ->get('hasCaption');
    }
    $form['#tree'] = TRUE;
    $form['#attached']['library'][] = 'editor/drupal.editor.dialog';
    $form['#prefix'] = '<div id="editor-media-dialog-form">';
    $form['#suffix'] = '</div>';
    $filters = $editor
      ->getFilterFormat()
      ->filters();
    $filter_html = $filters
      ->get('filter_html');
    $filter_align = $filters
      ->get('filter_align');
    $filter_caption = $filters
      ->get('filter_caption');
    $media_embed_filter = $filters
      ->get('media_embed');
    $allowed_attributes = [];
    if ($filter_html->status) {
      $restrictions = $filter_html
        ->getHTMLRestrictions();
      $allowed_attributes = $restrictions['allowed']['drupal-media'];
    }
    $media = $this->entityRepository
      ->loadEntityByUuid('media', $media_embed_element['data-entity-uuid']);
    if ($image_field_name = $this
      ->getMediaImageSourceFieldName($media)) {

      // We'll want the alt text from the same language as the host.
      if (!empty($editor_object['hostEntityLangcode']) && $media
        ->hasTranslation($editor_object['hostEntityLangcode'])) {
        $media = $media
          ->getTranslation($editor_object['hostEntityLangcode']);
      }
      $settings = $media->{$image_field_name}
        ->getItemDefinition()
        ->getSettings();
      $alt = isset($media_embed_element['alt']) ? $media_embed_element['alt'] : NULL;
      $form['alt'] = [
        '#type' => 'textfield',
        '#title' => $this
          ->t('Alternate text'),
        '#default_value' => $alt,
        '#description' => $this
          ->t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'),
        '#required_error' => $this
          ->t('Alternative text is required.<br />(Only in rare cases should this be left empty. To create empty alternative text, enter <code>""</code> — two double quotes without any content).'),
        '#maxlength' => 2048,
        '#placeholder' => $media->{$image_field_name}->alt,
        '#parents' => [
          'attributes',
          'alt',
        ],
        '#access' => !empty($settings['alt_field']) && ($filter_html->status === FALSE || !empty($allowed_attributes['alt'])),
      ];
    }

    // When Drupal core's filter_align is being used, the text editor offers the
    // ability to change the alignment.
    $form['align'] = [
      '#title' => $this
        ->t('Align'),
      '#type' => 'radios',
      '#options' => [
        'none' => $this
          ->t('None'),
        'left' => $this
          ->t('Left'),
        'center' => $this
          ->t('Center'),
        'right' => $this
          ->t('Right'),
      ],
      '#default_value' => empty($media_embed_element['data-align']) ? 'none' : $media_embed_element['data-align'],
      '#attributes' => [
        'class' => [
          'container-inline',
        ],
      ],
      '#parents' => [
        'attributes',
        'data-align',
      ],
      '#access' => $filter_align->status && ($filter_html->status === FALSE || !empty($allowed_attributes['data-align'])),
    ];

    // When Drupal core's filter_caption is being used, the text editor offers
    // the ability to in-place edit the media's caption: show a toggle.
    $form['caption'] = [
      '#title' => $this
        ->t('Caption'),
      '#type' => 'checkbox',
      '#default_value' => $has_caption === 'true',
      '#parents' => [
        'hasCaption',
      ],
      '#access' => $filter_caption->status && ($filter_html->status === FALSE || !empty($allowed_attributes['data-caption'])),
    ];
    $view_mode_options = array_intersect_key($this->entityDisplayRepository
      ->getViewModeOptionsByBundle('media', $media
      ->bundle()), $media_embed_filter->settings['allowed_view_modes']);
    $default_view_mode = static::getViewModeDefaultValue($view_mode_options, $media_embed_filter, $media_embed_element['data-view-mode'] ?? NULL);
    $form['view_mode'] = [
      '#title' => $this
        ->t("Display"),
      '#type' => 'select',
      '#options' => $view_mode_options,
      '#default_value' => $default_view_mode,
      '#parents' => [
        'attributes',
        'data-view-mode',
      ],
      '#access' => count($view_mode_options) >= 2,
    ];

    // Store the default from the MediaEmbed filter, so that if the selected
    // view mode matches the default, we can drop the 'data-view-mode'
    // attribute.
    $form_state
      ->set('filter_default_view_mode', $media_embed_filter->settings['default_view_mode']);
    if ((empty($form['alt']) || $form['alt']['#access'] === FALSE) && $form['align']['#access'] === FALSE && $form['caption']['#access'] === FALSE && $form['view_mode']['#access'] === FALSE) {
      $format = $editor
        ->getFilterFormat();
      $warning = $this
        ->t('There is nothing to configure for this media.');
      $form['no_access_notice'] = [
        '#markup' => $warning,
      ];
      if ($format
        ->access('update')) {
        $tparams = [
          '@warning' => $warning,
          '@edit_url' => $format
            ->toUrl('edit-form')
            ->toString(),
          '%format' => $format
            ->label(),
        ];
        $form['no_access_notice']['#markup'] = $this
          ->t('@warning <a href="@edit_url">Edit the text format %format</a> to modify the attributes that can be overridden.', $tparams);
      }
    }
    $form['actions'] = [
      '#type' => 'actions',
    ];
    $form['actions']['save_modal'] = [
      '#type' => 'submit',
      '#value' => $this
        ->t('Save'),
      // No regular submit-handler. This form only works via JavaScript.
      '#submit' => [],
      '#ajax' => [
        'callback' => '::submitForm',
        'event' => 'click',
      ],
      // Prevent this hidden element from being tabbable.
      '#attributes' => [
        'tabindex' => -1,
      ],
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $response = new AjaxResponse();

    // When the `alt` attribute is set to two double quotes, transform it to the
    // empty string: two double quotes signify "empty alt attribute". See above.
    if (trim($form_state
      ->getValue([
      'attributes',
      'alt',
    ], '')) === '""') {
      $form_state
        ->setValue([
        'attributes',
        'alt',
      ], '""');
    }

    // The `alt` attribute is optional: if it isn't set, the default value
    // simply will not be overridden. It's important to set it to FALSE
    // instead of unsetting the value.  This way we explicitly inform
    // the client side about the new value.
    if ($form_state
      ->hasValue([
      'attributes',
      'alt',
    ]) && trim($form_state
      ->getValue([
      'attributes',
      'alt',
    ])) === '') {
      $form_state
        ->setValue([
        'attributes',
        'alt',
      ], FALSE);
    }

    // If the selected view mode matches the default on the filter, remove the
    // attribute.
    if (!empty($form_state
      ->get('filter_default_view_mode')) && $form_state
      ->getValue([
      'attributes',
      'data-view-mode',
    ]) === $form_state
      ->get('filter_default_view_mode')) {
      $form_state
        ->setValue([
        'attributes',
        'data-view-mode',
      ], FALSE);
    }
    if ($form_state
      ->getErrors()) {
      unset($form['#prefix'], $form['#suffix']);
      $form['status_messages'] = [
        '#type' => 'status_messages',
        '#weight' => -10,
      ];
      $response
        ->addCommand(new HtmlCommand('#editor-media-dialog-form', $form));
    }
    else {

      // Only send back the relevant values.
      $values = [
        'hasCaption' => $form_state
          ->getValue('hasCaption'),
        'attributes' => $form_state
          ->getValue('attributes'),
      ];
      $response
        ->addCommand(new EditorDialogSave($values));
      $response
        ->addCommand(new CloseModalDialogCommand());
    }
    return $response;
  }

  /**
   * Gets the default value for the view mode form element.
   *
   * @param array $view_mode_options
   *   The array of options for the view mode form element.
   * @param \Drupal\filter\Plugin\FilterInterface $media_embed_filter
   *   The media embed filter.
   * @param string $media_element_view_mode_attribute
   *   The data-view-mode attribute on the <drupal-media> element.
   *
   * @return string|null
   *   The default value for the view mode form element.
   */
  public static function getViewModeDefaultValue(array $view_mode_options, FilterInterface $media_embed_filter, $media_element_view_mode_attribute) {

    // The select element won't display without at least two options, so if
    // that's the case, just return NULL.
    if (count($view_mode_options) < 2) {
      return NULL;
    }
    $filter_default_view_mode = $media_embed_filter->settings['default_view_mode'];

    // If the current media embed ($media_embed_element) has a set view mode,
    // we want to use that as the default in the select form element,
    // otherwise we'll want to use the default for all embedded media.
    if (!empty($media_element_view_mode_attribute) && array_key_exists($media_element_view_mode_attribute, $view_mode_options)) {
      return $media_element_view_mode_attribute;
    }
    elseif (array_key_exists($filter_default_view_mode, $view_mode_options)) {
      return $filter_default_view_mode;
    }
    return NULL;
  }

  /**
   * Gets the name of an image media item's source field.
   *
   * @param \Drupal\media\MediaInterface $media
   *   The media item being embedded.
   *
   * @return string|null
   *   The name of the image source field configured for the media item, or
   *   NULL if the source field is not an image field.
   */
  protected function getMediaImageSourceFieldName(MediaInterface $media) {
    $field_definition = $media
      ->getSource()
      ->getSourceFieldDefinition($media->bundle->entity);
    $item_class = $field_definition
      ->getItemDefinition()
      ->getClass();
    if (is_a($item_class, ImageItem::class, TRUE)) {
      return $field_definition
        ->getName();
    }
    return NULL;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 2
DependencySerializationTrait::__wakeup public function 2
EditorMediaDialog::$entityDisplayRepository protected property The entity display repository.
EditorMediaDialog::$entityRepository protected property The entity repository.
EditorMediaDialog::buildForm public function Overrides FormInterface::buildForm
EditorMediaDialog::create public static function Instantiates a new instance of this class. Overrides FormBase::create
EditorMediaDialog::getFormId public function Returns a unique string identifying the form. Overrides FormInterface::getFormId
EditorMediaDialog::getMediaImageSourceFieldName protected function Gets the name of an image media item's source field.
EditorMediaDialog::getViewModeDefaultValue public static function Gets the default value for the view mode form element.
EditorMediaDialog::submitForm public function Form submission handler. Overrides FormInterface::submitForm
EditorMediaDialog::__construct public function Constructs a EditorMediaDialog object.
FormBase::$configFactory protected property The config factory. 3
FormBase::$requestStack protected property The request stack. 1
FormBase::$routeMatch protected property The route match.
FormBase::config protected function Retrieves a configuration object.
FormBase::configFactory protected function Gets the config factory for this form. 3
FormBase::container private function Returns the service container.
FormBase::currentUser protected function Gets the current user.
FormBase::getRequest protected function Gets the request object.
FormBase::getRouteMatch protected function Gets the route match.
FormBase::logger protected function Gets the logger for a specific channel.
FormBase::redirect protected function Returns a redirect response object for the specified route.
FormBase::resetConfigFactory public function Resets the configuration factory.
FormBase::setConfigFactory public function Sets the config factory for this form.
FormBase::setRequestStack public function Sets the request stack object to use.
FormBase::validateForm public function Form validation handler. Overrides FormInterface::validateForm 72
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.