You are here

lightning_media.module in Lightning Media 8.3

Core media asset support for Lightning.

File

lightning_media.module
View source
<?php

/**
 * @file
 * Core media asset support for Lightning.
 */
use Drupal\ckeditor\Ajax\AddStyleSheetCommand;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\WidgetInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup as Plural;
use Drupal\entity_browser\Element\EntityBrowserElement;
use Drupal\entity_browser\Entity\EntityBrowser;
use Drupal\file\FileInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\image\Plugin\Field\FieldWidget\ImageWidget;
use Drupal\image\Plugin\ImageEffect\ResizeImageEffect;
use Drupal\lightning_core\BundleEntityStorage;
use Drupal\lightning_core\OverrideHelper as Override;
use Drupal\lightning_media\Exception\IndeterminateBundleException;
use Drupal\lightning_media\Form\MediaForm;
use Drupal\lightning_media\ImageWidgetHelper;
use Drupal\lightning_media\MediaHelper;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Drupal\views\ViewEntityInterface;

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function lightning_media_form_media_type_form_alter(array &$form, FormStateInterface $form_state) {
  $form['source_dependent'] += [
    'source_configuration' => [],
  ];
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function lightning_media_view_insert(ViewEntityInterface $view) {

  // @todo Remove this check when Drupal 8.7 is no longer supported.
  if (method_exists(InstallerKernel::class, 'installationAttempted')) {
    $installation_attempted = InstallerKernel::installationAttempted();
  }
  else {
    $installation_attempted = call_user_func('drupal_installation_attempted');
  }
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  elseif ($installation_attempted && $view
    ->id() == 'media') {
    $display_id = $view
      ->addDisplay('entity_browser', 'Browser');
    $display =& $view
      ->getDisplay($display_id);
    $display = array_merge($display, unserialize('a:2:{s:15:"display_options";a:12:{s:17:"display_extenders";a:0:{}s:5:"style";a:2:{s:4:"type";s:4:"grid";s:7:"options";a:8:{s:8:"grouping";a:0:{}s:7:"columns";i:4;s:15:"automatic_width";b:1;s:9:"alignment";s:10:"horizontal";s:17:"col_class_default";b:1;s:16:"col_class_custom";s:0:"";s:17:"row_class_default";b:1;s:16:"row_class_custom";s:0:"";}}s:8:"defaults";a:9:{s:5:"style";b:0;s:3:"row";b:0;s:6:"fields";b:0;s:7:"filters";b:0;s:13:"filter_groups";b:0;s:5:"empty";b:0;s:5:"pager";b:0;s:9:"css_class";b:0;s:9:"arguments";b:0;}s:3:"row";a:2:{s:4:"type";s:6:"fields";s:7:"options";a:0:{}}s:6:"fields";a:2:{s:20:"thumbnail__target_id";a:37:{s:2:"id";s:20:"thumbnail__target_id";s:5:"table";s:16:"media_field_data";s:5:"field";s:20:"thumbnail__target_id";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:1;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:0:"";s:23:"element_default_classes";b:1;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:17:"click_sort_column";s:9:"target_id";s:4:"type";s:5:"image";s:8:"settings";a:2:{s:11:"image_style";s:6:"medium";s:10:"image_link";s:0:"";}s:12:"group_column";s:0:"";s:13:"group_columns";a:0:{}s:10:"group_rows";b:1;s:11:"delta_limit";i:0;s:12:"delta_offset";i:0;s:14:"delta_reversed";b:0;s:16:"delta_first_last";b:0;s:10:"multi_type";s:9:"separator";s:9:"separator";s:2:", ";s:17:"field_api_classes";b:0;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:9:"thumbnail";s:9:"plugin_id";s:5:"field";}s:21:"entity_browser_select";a:23:{s:2:"id";s:21:"entity_browser_select";s:5:"table";s:5:"media";s:5:"field";s:21:"entity_browser_select";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"label";s:0:"";s:7:"exclude";b:0;s:5:"alter";a:26:{s:10:"alter_text";b:0;s:4:"text";s:0:"";s:9:"make_link";b:0;s:4:"path";s:0:"";s:8:"absolute";b:0;s:8:"external";b:0;s:14:"replace_spaces";b:0;s:9:"path_case";s:4:"none";s:15:"trim_whitespace";b:0;s:3:"alt";s:0:"";s:3:"rel";s:0:"";s:10:"link_class";s:0:"";s:6:"prefix";s:0:"";s:6:"suffix";s:0:"";s:6:"target";s:0:"";s:5:"nl2br";b:0;s:10:"max_length";i:0;s:13:"word_boundary";b:1;s:8:"ellipsis";b:1;s:9:"more_link";b:0;s:14:"more_link_text";s:0:"";s:14:"more_link_path";s:0:"";s:10:"strip_tags";b:0;s:4:"trim";b:0;s:13:"preserve_tags";s:0:"";s:4:"html";b:0;}s:12:"element_type";s:0:"";s:13:"element_class";s:0:"";s:18:"element_label_type";s:0:"";s:19:"element_label_class";s:0:"";s:19:"element_label_colon";b:0;s:20:"element_wrapper_type";s:0:"";s:21:"element_wrapper_class";s:15:"visually-hidden";s:23:"element_default_classes";b:0;s:5:"empty";s:0:"";s:10:"hide_empty";b:0;s:10:"empty_zero";b:0;s:16:"hide_alter_empty";b:1;s:11:"entity_type";s:5:"media";s:9:"plugin_id";s:21:"entity_browser_select";}}s:7:"filters";a:4:{s:6:"status";a:16:{s:2:"id";s:6:"status";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"status";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:10:{s:11:"operator_id";s:0:"";s:5:"label";s:4:"True";s:11:"description";N;s:12:"use_operator";b:0;s:8:"operator";s:9:"status_op";s:10:"identifier";s:6:"status";s:8:"required";b:1;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}}s:10:"is_grouped";b:1;s:10:"group_info";a:10:{s:5:"label";s:17:"Publishing status";s:11:"description";s:0:"";s:10:"identifier";s:6:"status";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:2:{i:1;a:3:{s:5:"title";s:9:"Published";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";}i:2;a:3:{s:5:"title";s:11:"Unpublished";s:8:"operator";s:1:"=";s:5:"value";s:1:"0";}}}s:9:"plugin_id";s:7:"boolean";s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"status";}s:4:"name";a:16:{s:2:"id";s:4:"name";s:5:"table";s:16:"media_field_data";s:5:"field";s:4:"name";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:8:"contains";s:5:"value";s:0:"";s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:10:{s:11:"operator_id";s:7:"name_op";s:5:"label";s:8:"Keywords";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:7:"name_op";s:10:"identifier";s:8:"keywords";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:10:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";s:12:"page_creator";s:1:"0";s:14:"layout_manager";s:1:"0";s:13:"page_reviewer";s:1:"0";s:20:"landing_page_creator";s:1:"0";s:21:"landing_page_reviewer";s:1:"0";s:13:"media_creator";s:1:"0";s:13:"media_manager";s:1:"0";}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:4:"name";s:9:"plugin_id";s:6:"string";}s:6:"bundle";a:16:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:2:"in";s:5:"value";a:0:{}s:5:"group";i:1;s:7:"exposed";b:1;s:6:"expose";a:12:{s:11:"operator_id";s:9:"bundle_op";s:5:"label";s:4:"Type";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:9:"bundle_op";s:10:"identifier";s:4:"type";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:10:{s:13:"authenticated";s:13:"authenticated";s:9:"anonymous";s:1:"0";s:13:"administrator";s:1:"0";s:12:"page_creator";s:1:"0";s:14:"layout_manager";s:1:"0";s:13:"page_reviewer";s:1:"0";s:20:"landing_page_creator";s:1:"0";s:21:"landing_page_reviewer";s:1:"0";s:13:"media_creator";s:1:"0";s:13:"media_manager";s:1:"0";}s:6:"reduce";b:0;s:8:"argument";s:6:"bundle";}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"bundle";}s:28:"field_media_in_library_value";a:14:{s:2:"id";s:28:"field_media_in_library_value";s:5:"table";s:29:"media__field_media_in_library";s:5:"field";s:28:"field_media_in_library_value";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:8:"operator";s:1:"=";s:5:"value";s:1:"1";s:5:"group";i:1;s:7:"exposed";b:0;s:6:"expose";a:10:{s:11:"operator_id";s:0:"";s:5:"label";s:0:"";s:11:"description";s:0:"";s:12:"use_operator";b:0;s:8:"operator";s:0:"";s:10:"identifier";s:0:"";s:8:"required";b:0;s:8:"remember";b:0;s:8:"multiple";b:0;s:14:"remember_roles";a:1:{s:13:"authenticated";s:13:"authenticated";}}s:10:"is_grouped";b:0;s:10:"group_info";a:10:{s:5:"label";s:0:"";s:11:"description";s:0:"";s:10:"identifier";s:0:"";s:8:"optional";b:1;s:6:"widget";s:6:"select";s:8:"multiple";b:0;s:8:"remember";b:0;s:13:"default_group";s:3:"All";s:22:"default_group_multiple";a:0:{}s:11:"group_items";a:0:{}}s:9:"plugin_id";s:7:"boolean";}}s:13:"filter_groups";a:2:{s:8:"operator";s:3:"AND";s:6:"groups";a:1:{i:1;s:3:"AND";}}s:5:"empty";a:1:{s:16:"area_text_custom";a:10:{s:2:"id";s:16:"area_text_custom";s:5:"table";s:5:"views";s:5:"field";s:16:"area_text_custom";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:5:"empty";b:1;s:8:"tokenize";b:0;s:7:"content";s:36:"There are no media items to display.";s:9:"plugin_id";s:11:"text_custom";}}s:5:"pager";a:2:{s:4:"type";s:15:"infinite_scroll";s:7:"options";a:7:{s:14:"items_per_page";i:12;s:6:"offset";i:0;s:2:"id";i:0;s:11:"total_pages";N;s:4:"tags";a:2:{s:8:"previous";s:12:"‹ Previous";s:4:"next";s:8:"Next ›";}s:6:"expose";a:7:{s:14:"items_per_page";b:0;s:20:"items_per_page_label";s:14:"Items per page";s:22:"items_per_page_options";s:13:"5, 10, 25, 50";s:26:"items_per_page_options_all";b:0;s:32:"items_per_page_options_all_label";s:7:"- All -";s:6:"offset";b:0;s:12:"offset_label";s:6:"Offset";}s:21:"views_infinite_scroll";a:2:{s:11:"button_text";s:9:"Load More";s:26:"automatically_load_content";b:0;}}}s:19:"display_description";s:0:"";s:9:"css_class";s:8:"eb-media";s:9:"arguments";a:1:{s:6:"bundle";a:27:{s:2:"id";s:6:"bundle";s:5:"table";s:16:"media_field_data";s:5:"field";s:6:"bundle";s:12:"relationship";s:4:"none";s:10:"group_type";s:5:"group";s:11:"admin_label";s:0:"";s:14:"default_action";s:7:"default";s:9:"exception";a:3:{s:5:"value";s:3:"all";s:12:"title_enable";b:0;s:5:"title";s:3:"All";}s:12:"title_enable";b:0;s:5:"title";s:0:"";s:21:"default_argument_type";s:29:"entity_browser_widget_context";s:24:"default_argument_options";a:3:{s:11:"context_key";s:14:"target_bundles";s:8:"fallback";s:3:"all";s:8:"multiple";s:2:"or";}s:25:"default_argument_skip_url";b:0;s:15:"summary_options";a:4:{s:9:"base_path";s:0:"";s:5:"count";b:1;s:14:"items_per_page";i:25;s:8:"override";b:0;}s:7:"summary";a:3:{s:10:"sort_order";s:3:"asc";s:17:"number_of_records";i:0;s:6:"format";s:15:"default_summary";}s:18:"specify_validation";b:1;s:8:"validate";a:2:{s:4:"type";s:17:"entity:media_type";s:4:"fail";s:6:"ignore";}s:16:"validate_options";a:4:{s:9:"operation";s:4:"view";s:8:"multiple";i:1;s:6:"access";b:0;s:7:"bundles";a:0:{}}s:8:"glossary";b:0;s:5:"limit";i:0;s:4:"case";s:4:"none";s:9:"path_case";s:4:"none";s:14:"transform_dash";b:0;s:12:"break_phrase";b:1;s:11:"entity_type";s:5:"media";s:12:"entity_field";s:6:"bundle";s:9:"plugin_id";s:6:"string";}}}s:14:"cache_metadata";a:3:{s:7:"max-age";i:-1;s:8:"contexts";a:5:{i:0;s:26:"languages:language_content";i:1;s:28:"languages:language_interface";i:2;s:3:"url";i:3;s:14:"url.query_args";i:4;s:16:"user.permissions";}s:4:"tags";a:0:{}}}'));

    // This line is needed because Views is insane.
    $view
      ->getExecutable()->current_display = NULL;

    // Media library only modifies the URL of the media view in hook_install();
    // we need to make sure the URL is always modified. This basically copies
    // the code of media_library_install() on Drupal 8.7 and earlier. We need to
    // check the Drupal core version because Media Library doesn't change the
    // path as of Drupal 8.8, and therefore neither should we.
    $display =& $view
      ->getDisplay('media_page_list');
    if (version_compare(Drupal::VERSION, '8.8.0', '<') && $display) {
      $display['display_options']['path'] = 'admin/content/media-table';
      unset($display['display_options']['menu']);
    }
    $view
      ->save();

    // Add the view to both bundled entity browsers.
    $browsers = [
      'ckeditor_media_browser',
      'media_browser',
    ];
    foreach ($browsers as $browser) {

      /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
      $browser = EntityBrowser::load($browser);
      if ($browser) {
        $browser
          ->addWidget([
          'id' => 'view',
          'label' => 'Library',
          'weight' => -10,
          'settings' => [
            'view' => 'media',
            'view_display' => $display_id,
            'submit_text' => 'Place',
            'auto_select' => FALSE,
          ],
        ]);
        $browser
          ->save();
      }
    }
  }
}

/**
 * Implements hook_field_widget_third_party_settings_form().
 */
function lightning_media_field_widget_third_party_settings_form(WidgetInterface $widget) {
  $element = [];
  if ($widget instanceof ImageWidget) {
    $element = ImageWidgetHelper::getSettingsForm($widget);
  }
  return $element;
}

/**
 * Implements hook_field_widget_settings_summary_alter().
 */
function lightning_media_field_widget_settings_summary_alter(array &$summary, array $context) {
  if ($context['widget'] instanceof ImageWidget) {
    ImageWidgetHelper::summarize($context['widget'], $summary);
  }
}

/**
 * Implements hook_field_widget_form_alter().
 */
function lightning_media_field_widget_form_alter(array &$element, FormStateInterface $form_state, array $context) {
  if ($context['widget'] instanceof ImageWidget) {
    ImageWidgetHelper::alter($element, $context['widget']);
  }
}

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter().
 */
function lightning_media_field_widget_entity_browser_entity_reference_form_alter(array &$element, FormStateInterface $form_state, array $context) {

  // Move the remaining number of selections to the details summary.
  if (isset($element['current']['#prefix'])) {
    $element['#description'] .= $element['current']['#prefix'];
    unset($element['current']['#prefix']);
  }
  if (!empty($element['current']['items'])) {

    // Wrap the current selections in a nice <details> element.
    $cardinality = (int) $context['items']
      ->getFieldDefinition()
      ->getFieldStorageDefinition()
      ->getCardinality();
    $element['current']['#theme_wrappers'] = [
      'details' => [
        '#attributes' => [
          'open' => TRUE,
        ],
        '#title' => new Plural($cardinality, 'Current selection', 'Current selections'),
        '#summary_attributes' => [],
      ],
    ];
  }
}

/**
 * Implements hook_modules_installed().
 */
function lightning_media_modules_installed(array $modules) {

  // Don't do anything during config sync.
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  if (in_array('lightning_roles', $modules)) {
    \Drupal::service('lightning.content_roles')
      ->grantPermissions('creator', [
      'use text format rich_text',
      'access ckeditor_media_browser entity browser pages',
      'access media_browser entity browser pages',
    ]);
  }
}

/**
 * Implements hook_element_info_alter().
 */
function lightning_media_element_info_alter(array &$info) {
  $info['entity_browser']['#after_build'][] = 'lightning_media_inject_entity_browser_count';
}

/**
 * Implements hook_entity_extra_field_info().
 */
function lightning_media_entity_extra_field_info() {
  $extra_fields = [];
  if (\Drupal::moduleHandler()
    ->moduleExists('media')) {

    /** @var \Drupal\media\MediaTypeInterface $media_type */
    foreach (MediaType::loadMultiple() as $id => $media_type) {
      $plugin_definition = $media_type
        ->getSource()
        ->getPluginDefinition();
      if (isset($plugin_definition['preview'])) {
        $extra_fields['media'][$id]['form']['preview'] = [
          'label' => t('Preview'),
          'description' => t('A live preview of the @media_type.', [
            '@media_type' => $media_type
              ->label(),
          ]),
          'weight' => 0,
        ];
      }
    }
  }
  return $extra_fields;
}

/**
 * Implements hook_inline_entity_form_entity_form_alter().
 */
function lightning_media_inline_entity_form_entity_form_alter(array &$entity_form) {
  $entity = $entity_form['#entity'];
  if ($entity instanceof MediaInterface && MediaHelper::isPreviewable($entity)) {
    $entity_form['preview'] = MediaHelper::getSourceField($entity)
      ->view('default');
  }
}

/**
 * Validates a file using media entity source field criteria.
 *
 * @param \Drupal\file\FileInterface $file
 *   The file to validate.
 * @param string[] $bundles
 *   (optional) A set of media bundle IDs which might match the input. If
 *   omitted, all bundles to which the user has create access will be checked.
 *
 * @return string[]
 *   An array of errors. If empty, the file passed validation.
 */
function lightning_media_validate_upload(FileInterface $file, array $bundles = []) {
  try {
    $entity = \Drupal::service('lightning.media_helper')
      ->createFromInput($file, $bundles);
  } catch (IndeterminateBundleException $e) {
    return [];
  }

  /** @var \Drupal\file\Plugin\Field\FieldType\FileItem $item */
  $item = MediaHelper::getSourceField($entity)
    ->first();
  $validators = [
    // It's maybe a bit overzealous to run this validator, but hey...better
    // safe than screwed over by script kiddies.
    'file_validate_name_length' => [],
  ];
  $validators = array_merge($validators, $item
    ->getUploadValidators());

  // This function is only called by the custom FileUpload widget, which runs
  // file_validate_extensions before this function. So there's no need to
  // validate the extensions again.
  unset($validators['file_validate_extensions']);

  // If this is an image field, add image validation. Against all sanity,
  // this is normally done by ImageWidget, not ImageItem, which is why we
  // need to facilitate this a bit.
  if ($item instanceof ImageItem) {

    // Validate that this is, indeed, a supported image.
    $validators['file_validate_is_image'] = [];
    $settings = $item
      ->getFieldDefinition()
      ->getSettings();
    if ($settings['max_resolution'] || $settings['min_resolution']) {
      $validators['file_validate_image_resolution'] = [
        $settings['max_resolution'],
        $settings['min_resolution'],
      ];
    }
  }
  return file_validate($file, $validators);
}

/**
 * Post-build callback for entity browser elements.
 *
 * This function injects the number of default values the entity browser has
 * into its JavaScript settings so that several instances of an entity browser
 * on a particular field can respect the field's cardinality. This is used by
 * our special-sauce JavaScript in browser.js to ensure that you cannot select
 * more entities than the cardinality will allow.
 *
 * @param array $element
 *   The fully built element.
 *
 * @return array
 *   The processed element.
 */
function lightning_media_inject_entity_browser_count(array $element) {
  $settings =& $element['#attached']['drupalSettings']['entity_browser'];
  $uuid = key($settings);
  $settings[$uuid]['count'] = count($element['#default_value']);
  return $element;
}

/**
 * Implements template_preprocess_image_style().
 *
 * @param array $variables
 *   Template variables.
 */
function lightning_media_preprocess_image_style(array &$variables) {
  $extension = pathinfo($variables['uri'], PATHINFO_EXTENSION);
  $extension = mb_strtolower($extension);

  // If this is an SVG and we don't know its dimensions, try to calculate them
  // through the image style's effect chain.
  if ($extension == 'svg' && (empty($variables['image']['#width']) || empty($variables['image']['#height']))) {
    $image_style = ImageStyle::load($variables['style_name']);

    // Loop through the effect chain, collecting configured dimensions for all
    // resizing effects.
    $dimensions = [];
    foreach ($image_style
      ->getEffects() as $effect) {
      if ($effect instanceof ResizeImageEffect) {
        $configuration = $effect
          ->getConfiguration();
        array_push($dimensions, [
          'width' => $configuration['data']['width'],
          'height' => $configuration['data']['height'],
        ]);
      }
    }

    // If we didn't collect any dimensions, there's nothing else to be done.
    if (empty($dimensions)) {
      return;
    }

    // Sort the configured dimensions in ascending order by an arbitrary axis,
    // which can be 'width' or 'height'.
    $axis = @$variables['sort_axis'] ?: 'width';
    usort($dimensions, function (array $a, array $b) use ($axis) {
      return $b[$axis] - $a[$axis];
    });

    // Start with the widest set of configured dimensions.
    $dimensions = end($dimensions);

    // Allow the image style to transform the dimensions as needed.
    $image_style
      ->transformDimensions($dimensions, $variables['uri']);
    if ($dimensions['width']) {
      $variables['image']['#width'] = $dimensions['width'];
    }
    if ($dimensions['height']) {
      $variables['image']['#height'] = $dimensions['height'];
    }

    // If at least one dimension is known, display the image.
    $variables['image']['#access'] = $dimensions['width'] || $dimensions['height'];
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function lightning_media_form_entity_browser_media_browser_form_alter(array &$form) {
  $form['#attached']['library'][] = 'lightning_media/browser.styling';
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function lightning_media_form_entity_browser_ckeditor_media_browser_form_alter(array &$form) {
  lightning_media_form_entity_browser_media_browser_form_alter($form);
}

/**
 * Implements hook_entity_base_field_info_alter().
 */
function lightning_media_entity_base_field_info_alter(array &$fields, EntityTypeInterface $entity_type) {
  if ($entity_type
    ->id() === 'media' && array_key_exists('revision_log_message', $fields)) {
    $fields['revision_log_message']
      ->setDisplayConfigurable('form', TRUE);
  }
}

/**
 * Implements hook_entity_type_alter().
 */
function lightning_media_entity_type_alter(array &$entity_types) {

  // Media items are a common reference target, and that's that...
  $entity_types['media']
    ->set('common_reference_target', TRUE);

  // Use a configuration flag to determine whether or not to show the revision
  // UI.
  $entity_types['media']
    ->set('show_revision_ui', (bool) Drupal::config('lightning_media.settings')
    ->get('revision_ui'));

  // Use our specialized entity form for adding and editing media assets in
  // order to support dynamic preview generation.
  Override::entityForm($entity_types['media'], MediaForm::class);
  Override::entityForm($entity_types['media'], MediaForm::class, 'edit');

  // Use our extended storage handler for media bundles.
  Override::entityHandler($entity_types['media_type'], 'storage', BundleEntityStorage::class);
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function lightning_media_media_type_insert(MediaTypeInterface $media_type) {

  // Don't do anything during config sync.
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  \Drupal::entityTypeManager()
    ->getStorage('field_config')
    ->create([
    'field_name' => 'field_media_in_library',
    'entity_type' => 'media',
    'bundle' => $media_type
      ->id(),
    'label' => t('Show in media library'),
    'settings' => [
      'on_label' => t('Shown in media library'),
      'off_label' => t('Hidden in media library'),
    ],
    'default_value' => [
      [
        'value' => TRUE,
      ],
    ],
  ])
    ->save();
  lightning_media_entity_get_form_display('media', $media_type
    ->id())
    ->setComponent('field_media_in_library', [
    'type' => 'boolean_checkbox',
    'settings' => [
      'display_label' => TRUE,
    ],
  ])
    ->save();
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function lightning_media_form_entity_embed_dialog_alter(array &$form, FormStateInterface $form_state) {
  list($editor, $embed_button) = $form_state
    ->getBuildInfo()['args'];

  /** @var \Drupal\embed\EmbedButtonInterface $embed_button */
  if ($embed_button
    ->id() == 'media_browser') {
    $element =& $form['attributes']['data-entity-embed-settings']['view_mode'];
    if (isset($element['#options']['embedded'])) {
      $element['#default_value'] = 'embedded';
    }
  }
}

/**
 * Implements hook_js_settings_alter().
 */
function lightning_media_js_settings_alter(array &$settings) {
  if (empty($settings['ajax'])) {
    $settings['ajax'] = [];
  }
  $route_name = \Drupal::routeMatch()
    ->getRouteName();
  if (strpos($route_name, 'entity_browser') === 0 && isset($settings['ajaxPageState']['libraries'])) {
    $libraries = explode(',', $settings['ajaxPageState']['libraries']);

    // If we pretend EB's iframe library has not been previously loaded, it will
    // ALWAYS be fetched from the server, preventing (in a crappy, kludgey way)
    // the bug in #2768849.
    $libraries = array_diff($libraries, [
      'entity_browser/iframe',
    ]);
    $settings['ajaxPageState']['libraries'] = implode(',', $libraries);
  }
}

/**
 * Implements hook_ajax_render_alter().
 */
function lightning_media_ajax_render_alter(array &$data) {
  $route = \Drupal::routeMatch()
    ->getRouteName();
  $query = \Drupal::request()->query;
  if ($route == 'entity_embed.dialog') {
    foreach ($data as &$command) {
      if ($command['command'] == 'settings' && isset($command['settings']['ajaxPageState']['libraries'])) {
        $libraries = explode(',', $command['settings']['ajaxPageState']['libraries']);
        $libraries = array_diff($libraries, [
          'entity_browser/iframe',
        ]);
        $command['settings']['ajaxPageState']['libraries'] = implode(',', $libraries);
      }
    }
  }
  elseif ($route == 'embed.preview' && $query
    ->has('editor')) {
    $style_sheets = [];
    foreach ($data as $command) {

      // Any CSS being added should be replicated in the editor.
      if ($command['command'] == 'add_css') {
        $matched = [];

        // Assume (perhaps naively) that all the style sheets are embedded as
        // <link /> tags.
        preg_match_all('/href="([^"]+)"/', $command['data'], $matched);
        $style_sheets = array_merge($style_sheets, $matched[1]);
      }
    }
    if ($style_sheets) {
      $command = new AddStyleSheetCommand($query
        ->get('editor'), $style_sheets);
      $data[] = $command
        ->render();
    }
  }
}

/**
 * Preprocess function for grid views of the media library.
 *
 * @param array $variables
 *   Template variables.
 */
function lightning_media_preprocess_views_view_grid(array &$variables) {

  /** @var \Drupal\views\ViewExecutable $view */
  $view = $variables['view'];
  if ($view->display_handler
    ->getPluginId() == 'entity_browser') {
    foreach ($variables['items'] as &$item) {
      foreach ($item['content'] as &$column) {
        $column['attributes']['data-selectable'] = 'true';
      }
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function lightning_media_entity_form_display_presave(EntityFormDisplayInterface $display) {

  // Don't do anything during config sync.
  if (\Drupal::isConfigSyncing()) {
    return;
  }
  if (!\Drupal::config('lightning_media.settings')
    ->get('entity_browser.override_widget')) {
    return;
  }
  $filter = function (FieldStorageDefinitionInterface $field) {
    return $field
      ->getType() == 'entity_reference' && $field
      ->getSetting('target_type') == 'media';
  };
  $new_components = \Drupal::service('lightning.display_helper')
    ->getNewFields($display, $filter);
  foreach ($new_components as $key => $component) {
    $display
      ->setComponent($key, [
      'type' => 'entity_browser_entity_reference',
      'weight' => $component['weight'],
      'settings' => [
        'entity_browser' => 'media_browser',
        'field_widget_display' => 'rendered_entity',
        'field_widget_edit' => TRUE,
        'field_widget_remove' => TRUE,
        'selection_mode' => EntityBrowserElement::SELECTION_MODE_APPEND,
        'field_widget_display_settings' => [
          'view_mode' => 'thumbnail',
        ],
        'open' => TRUE,
      ],
      'region' => 'content',
    ]);
  }
}

/**
 * Implements hook_library_info_alter().
 */
function lightning_media_library_info_alter(array &$libraries, $extension) {
  if ($extension === 'entity_browser') {

    // Clicking the "Remove" or "Edit" buttons in the media browser doesn't
    // trigger the event that makes Quick Edit display the "Save" button.
    // Loading the drupal.file library before entity browser attaches the
    // file button event handlers to the media browser buttons.
    // @see Drupal.behaviors.fileButtons
    $libraries['entity_reference']['dependencies'][] = 'file/drupal.file';
  }
}

/**
 * Returns the entity view display associated with a bundle and view mode.
 *
 * This is an exact copy of the deprecated entity_get_display() from Core 8.6.x
 *  except for one change: the default value of the $view_mode parameter.
 *
 * @todo Eliminate this in favor of
 *   \Drupal::service('entity_display.repository')->getViewDisplay() in Core
 *   8.8.x once that is the lowest supported version.
 *
 * @param string $entity_type
 *   The entity type.
 * @param string $bundle
 *   The bundle.
 * @param string $view_mode
 *   The view mode, or 'default' to retrieve the 'default' display object for
 *   this bundle.
 *
 * @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
 *   The entity view display associated with the view mode.
 *
 * @see \Drupal\Core\Entity\EntityStorageInterface::create()
 * @see \Drupal\Core\Entity\EntityStorageInterface::load()
 */
function lightning_media_entity_get_display($entity_type, $bundle, $view_mode = 'default') {

  // Try loading the display from configuration.
  $display = EntityViewDisplay::load($entity_type . '.' . $bundle . '.' . $view_mode);

  // If not found, create a fresh display object. We do not preemptively create
  // new entity_view_display configuration entries for each existing entity type
  // and bundle whenever a new view mode becomes available. Instead,
  // configuration entries are only created when a display object is explicitly
  // configured and saved.
  if (!$display) {
    $display = EntityViewDisplay::create([
      'targetEntityType' => $entity_type,
      'bundle' => $bundle,
      'mode' => $view_mode,
      'status' => TRUE,
    ]);
  }
  return $display;
}

/**
 * Returns the entity form display associated with a bundle and form mode.
 *
 * This is an exact copy of the deprecated entity_get_form_display() from Core
 * 8.6.x except for one change: the default value of the $form_mode parameter.
 *
 * @todo Eliminate this in favor of
 *   \Drupal::service('entity_display.repository')->getFormDisplay() in Core
 *   8.8.x once that is the lowest supported version.
 *
 * @param string $entity_type
 *   The entity type.
 * @param string $bundle
 *   The bundle.
 * @param string $form_mode
 *   The form mode.
 *
 * @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
 *   The entity form display associated with the given form mode.
 *
 * @see \Drupal\Core\Entity\EntityStorageInterface::create()
 * @see \Drupal\Core\Entity\EntityStorageInterface::load()
 */
function lightning_media_entity_get_form_display($entity_type, $bundle, $form_mode = 'default') {

  // Try loading the entity from configuration.
  $entity_form_display = EntityFormDisplay::load($entity_type . '.' . $bundle . '.' . $form_mode);

  // If not found, create a fresh entity object. We do not preemptively create
  // new entity form display configuration entries for each existing entity type
  // and bundle whenever a new form mode becomes available. Instead,
  // configuration entries are only created when an entity form display is
  // explicitly configured and saved.
  if (!$entity_form_display) {
    $entity_form_display = EntityFormDisplay::create([
      'targetEntityType' => $entity_type,
      'bundle' => $bundle,
      'mode' => $form_mode,
      'status' => TRUE,
    ]);
  }
  return $entity_form_display;
}

Functions

Namesort descending Description
lightning_media_ajax_render_alter Implements hook_ajax_render_alter().
lightning_media_element_info_alter Implements hook_element_info_alter().
lightning_media_entity_base_field_info_alter Implements hook_entity_base_field_info_alter().
lightning_media_entity_extra_field_info Implements hook_entity_extra_field_info().
lightning_media_entity_form_display_presave Implements hook_ENTITY_TYPE_presave().
lightning_media_entity_get_display Returns the entity view display associated with a bundle and view mode.
lightning_media_entity_get_form_display Returns the entity form display associated with a bundle and form mode.
lightning_media_entity_type_alter Implements hook_entity_type_alter().
lightning_media_field_widget_entity_browser_entity_reference_form_alter Implements hook_field_widget_WIDGET_TYPE_form_alter().
lightning_media_field_widget_form_alter Implements hook_field_widget_form_alter().
lightning_media_field_widget_settings_summary_alter Implements hook_field_widget_settings_summary_alter().
lightning_media_field_widget_third_party_settings_form Implements hook_field_widget_third_party_settings_form().
lightning_media_form_entity_browser_ckeditor_media_browser_form_alter Implements hook_form_FORM_ID_alter().
lightning_media_form_entity_browser_media_browser_form_alter Implements hook_form_FORM_ID_alter().
lightning_media_form_entity_embed_dialog_alter Implements hook_form_FORM_ID_alter().
lightning_media_form_media_type_form_alter Implements hook_form_BASE_FORM_ID_alter().
lightning_media_inject_entity_browser_count Post-build callback for entity browser elements.
lightning_media_inline_entity_form_entity_form_alter Implements hook_inline_entity_form_entity_form_alter().
lightning_media_js_settings_alter Implements hook_js_settings_alter().
lightning_media_library_info_alter Implements hook_library_info_alter().
lightning_media_media_type_insert Implements hook_ENTITY_TYPE_insert().
lightning_media_modules_installed Implements hook_modules_installed().
lightning_media_preprocess_image_style Implements template_preprocess_image_style().
lightning_media_preprocess_views_view_grid Preprocess function for grid views of the media library.
lightning_media_validate_upload Validates a file using media entity source field criteria.
lightning_media_view_insert Implements hook_ENTITY_TYPE_insert().