You are here

public static function MediaLibrary::processMediaLibrary in Media Library Form API Element 8

Same name and namespace in other branches
  1. 2.x src/Element/MediaLibrary.php \Drupal\media_library_form_element\Element\MediaLibrary::processMediaLibrary()

Expand the media_library_element into it's required sub-elements.

Parameters

array $element: The base form element render array.

\Drupal\Core\Form\FormStateInterface $form_state: The form state object.

array $complete_form: The complete form render array.

Return value

array The form element render array.

Throws

\Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException

\Drupal\Component\Plugin\Exception\PluginNotFoundException

File

src/Element/MediaLibrary.php, line 51

Class

MediaLibrary
Provides a Media library form element.

Namespace

Drupal\media_library_form_element\Element

Code

public static function processMediaLibrary(array &$element, FormStateInterface $form_state, array &$complete_form) : array {
  $default_value = NULL;
  $referenced_entities = [];
  if (!empty($element['#value'])) {
    $default_value = $element['#value'];
  }
  if (!empty($default_value['media_selection_id'])) {
    $entity_ids = [
      $default_value['media_selection_id'],
    ];
  }
  else {
    $entity_ids = array_filter(explode(',', $default_value));
  }
  if (!empty($entity_ids)) {
    $referenced_entities = \Drupal::entityTypeManager()
      ->getStorage('media')
      ->loadMultiple($entity_ids);
  }
  $view_builder = \Drupal::entityTypeManager()
    ->getViewBuilder('media');
  $allowed_media_type_ids = $element['#allowed_bundles'];
  $parents = $element['#parents'];
  $field_name = array_pop($parents);
  $attributes = $element['#attributes'] ?? [];

  // Create an ID suffix from the parents to make sure each widget is unique.
  $id_suffix = $parents ? '-' . implode('-', $parents) : '';
  $field_widget_id = implode('', array_filter([
    $field_name,
    $id_suffix,
  ]));
  $wrapper_id = $field_name . '-media-library-wrapper' . $id_suffix;
  $limit_validation_errors = [
    array_merge($parents, [
      $field_name,
    ]),
  ];
  $element = array_merge($element, [
    '#target_bundles' => !empty($allowed_media_type_ids) ? $allowed_media_type_ids : FALSE,
    '#cardinality' => $element['#cardinality'] ?? 1,
    '#attributes' => [
      'id' => $wrapper_id,
      'class' => [
        'media-library-form-element',
      ],
    ],
    '#modal_selector' => '#modal-media-library',
    '#attached' => [
      'library' => [
        'media_library_form_element/media_library_form_element',
        'media_library/view',
      ],
    ],
  ]);
  if (empty($referenced_entities)) {
    $element['empty_selection'] = [
      '#type' => 'html_tag',
      '#tag' => 'p',
      '#value' => t('No media item selected.'),
      '#attributes' => [
        'class' => [
          'media-library-form-element-empty-text',
        ],
      ],
    ];
  }
  $element['selection'] = [
    '#type' => 'container',
    '#attributes' => [
      'class' => [
        'js-media-library-selection',
        'media-library-selection',
      ],
    ],
  ];
  foreach ($referenced_entities as $delta => $referenced_entity) {
    $element['selection'][$delta] = [
      '#type' => 'container',
      '#attributes' => [
        'class' => [
          'media-library-item',
          'media-library-item--grid',
          'js-media-library-item',
        ],
        // Add the tabindex '-1' to allow the focus to be shifted to the next
        // media item when an item is removed. We set focus to the container
        // because we do not want to set focus to the remove button
        // automatically.
        // @see ::updateFormElement()
        'tabindex' => '-1',
        // Add a data attribute containing the delta to allow us to easily
        // shift the focus to a specific media item.
        // @see ::updateFormElement()
        'data-media-library-item-delta' => $delta,
      ],
      'preview' => [
        '#type' => 'container',
        'remove_button' => [
          '#type' => 'submit',
          '#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
          '#value' => t('Remove'),
          '#media_id' => $referenced_entity
            ->id(),
          '#attributes' => [
            'class' => [
              'media-library-item__remove',
            ],
            'aria-label' => t('Remove @label', [
              '@label' => $referenced_entity
                ->label(),
            ]),
          ],
          '#ajax' => [
            'callback' => [
              static::class,
              'updateFormElement',
            ],
            'wrapper' => $wrapper_id,
            'progress' => [
              'type' => 'throbber',
              'message' => t('Removing @label.', [
                '@label' => $referenced_entity
                  ->label(),
              ]),
            ],
          ],
          '#submit' => [
            [
              static::class,
              'removeItem',
            ],
          ],
          // Prevent errors in other widgets from preventing removal.
          '#limit_validation_errors' => $limit_validation_errors,
        ],
        // @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
        'rendered_entity' => $view_builder
          ->view($referenced_entity, 'media_library'),
        'target_id' => [
          '#type' => 'hidden',
          '#value' => $referenced_entity
            ->id(),
        ],
      ],
      'weight' => [
        '#type' => 'number',
        '#theme' => 'input__number__media_library_item_weight',
        '#title' => \Drupal::translation()
          ->translate('Weight'),
        '#default_value' => $delta,
        '#attributes' => [
          'class' => [
            'js-media-library-item-weight',
          ],
        ],
      ],
    ];
  }
  $cardinality_unlimited = $element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED;
  $remaining = $element['#cardinality'] - count($referenced_entities);

  // Inform the user of how many items are remaining.
  if (!$cardinality_unlimited) {
    if ($remaining) {
      $cardinality_message = \Drupal::translation()
        ->formatPlural($remaining, 'One media item remaining.', '@count media items remaining.');
    }
    else {
      $cardinality_message = \Drupal::translation()
        ->translate('The maximum number of media items have been selected.');
    }

    // Add a line break between the field message and the cardinality message.
    if (!empty($element['#description'])) {
      $element['#description'] .= '<br />' . $cardinality_message;
    }
    else {
      $element['#description'] = $cardinality_message;
    }
  }

  // Create a new media library URL with the correct state parameters.
  $selected_type_id = reset($allowed_media_type_ids);
  $remaining = $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining;

  // This particular media library opener needs some extra metadata for its
  // \Drupal\media_library\MediaLibraryOpenerInterface::getSelectionResponse()
  // to be able to target the element
  // whose 'data-media-library-form-element-value'
  // attribute is the same as $field_widget_id. The entity ID, entity type ID,
  // bundle, field name are used for access checking.
  $opener_parameters = [
    'field_widget_id' => $field_widget_id,
    'field_name' => $field_name,
  ];
  $state = MediaLibraryState::create('media_library.opener.form_element', $allowed_media_type_ids, $selected_type_id, $remaining, $opener_parameters);

  // Add a button that will load the Media library in a modal using AJAX.
  $element['media_library_open_button'] = [
    '#type' => 'button',
    '#value' => t('Add media'),
    '#name' => $field_name . '-media-library-open-button' . $id_suffix,
    '#attributes' => [
      'class' => [
        'media-library-open-button',
        'js-media-library-open-button',
      ],
      // The jQuery UI dialog automatically moves focus to the first :tabbable
      // element of the modal, so we need to disable refocus on the button.
      'data-disable-refocus' => 'true',
    ],
    '#media_library_state' => $state,
    '#ajax' => [
      'callback' => [
        static::class,
        'openMediaLibrary',
      ],
      'progress' => [
        'type' => 'throbber',
        'message' => t('Opening media library.'),
      ],
    ],
    // Allow the media library to be opened even if there are form errors.
    '#limit_validation_errors' => [],
  ];

  // When the user returns from the modal to the widget, we want to shift the
  // focus back to the open button. If the user is not allowed to add more
  // items, the button needs to be disabled. Since we can't shift the focus to
  // disabled elements, the focus is set back to the open button via
  // JavaScript by adding the 'data-disabled-focus' attribute.
  // @see Drupal.behaviors.MediaLibraryWidgetDisableButton
  if (!$cardinality_unlimited && $remaining === 0) {
    $element['media_library_open_button']['#attributes']['data-disabled-focus'] = 'true';
    $element['media_library_open_button']['#attributes']['class'][] = 'visually-hidden';
  }

  // This hidden field and button are used to add new items to the widget.
  $element['media_library_selection'] = [
    '#type' => 'hidden',
    '#attributes' => array_merge([
      'data-media-library-form-element-value' => $field_widget_id,
    ], $attributes),
    '#default_value' => $element['#value'],
  ];

  // When a selection is made this hidden button is pressed to add new media
  // items based on the "media_library_selection" value.
  $element['media_library_update_widget'] = [
    '#type' => 'submit',
    '#value' => t('Update widget'),
    '#name' => $field_name . '-media-library-update' . $id_suffix,
    '#ajax' => [
      'callback' => [
        static::class,
        'updateFormElement',
      ],
      'wrapper' => $wrapper_id,
      'progress' => [
        'type' => 'throbber',
        'message' => t('Adding selection.'),
      ],
    ],
    '#attributes' => [
      'data-media-library-form-element-update' => $field_widget_id,
      'class' => [
        'js-hide',
      ],
    ],
    '#validate' => [
      [
        static::class,
        'validateItem',
      ],
    ],
    '#submit' => [
      [
        static::class,
        'updateItem',
      ],
    ],
    // Prevent errors in other widgets from preventing updates.
    // Exclude other validations in case there is no data yet.
    '#limit_validation_errors' => !empty($referenced_entities) ? $limit_validation_errors : [],
  ];
  return $element;
}