You are here

media_entity.install in Media entity 8.2

Same filename and directory in other branches
  1. 8 media_entity.install

Install, uninstall and update hooks for Media entity module.

File

media_entity.install
View source
<?php

/**
 * @file
 * Install, uninstall and update hooks for Media entity module.
 */
use Drupal\Core\Utility\UpdateException;
use Drupal\Core\Config\Entity\Query\QueryFactory;
use Drupal\Core\Database\Database;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;

/**
 * Checks if required version of the Entity API is installed.
 *
 * @return int
 *   REQUIREMENT_OK if dependency is met and REQUIREMENT_ERROR if not.
 *   REQUIREMENT_WARNING is returned if it can not be determined.
 */
function _media_entity_check_entity_version() {
  if (\Drupal::moduleHandler()
    ->moduleExists('entity')) {
    $info = system_get_info('module', 'entity');
    if (is_null($info['version'])) {
      return REQUIREMENT_WARNING;
    }
    if (version_compare($info['version'], '8.x-1.0-alpha3') >= 0) {
      return REQUIREMENT_ERROR;
    }
  }
  return REQUIREMENT_OK;
}

/**
 * Checks if all contrib modules depending on media_entity were updated.
 *
 * @return array
 *   An empty array if all modules that depend on media_entity were updated, or
 *   an array of incompatible modules otherwise. This array will be keyed by
 *   either "providers" or "modules", depending if the incompatible module is a
 *   contrib module that provides a type plugin (in which case it is expected to
 *   have been upgraded to its 2.x branch) or just a module that depends on
 *   "media_entity", respectively.
 */
function _media_entity_get_incompatible_modules() {
  \Drupal::service('plugin.cache_clearer')
    ->clearCachedDefinitions();
  $incompatible_modules = [];

  // Modules that provide provider plugins need to have ben updated and be
  // implementing now @MediaSource instead of @MediaType plugins.
  $old_plugins = \Drupal::service('plugin.manager.media_entity.type')
    ->getDefinitions();

  // The main media_entity module defines a "generic" type. We are directly
  // handling this provider's configs in the update hook.
  unset($old_plugins['generic']);
  foreach ($old_plugins as $definition) {
    $incompatible_modules['providers'][$definition['provider']] = $definition['provider'];
  }

  // None of the enabled modules in the system should at this point depend on
  // media_entity anymore.

  /** @var \Drupal\Core\Extension\Extension[] $module_list */
  $module_list = \Drupal::moduleHandler()
    ->getModuleList();
  foreach (array_keys($module_list) as $module_name) {
    $info = system_get_info('module', $module_name);
    if (!empty($info['dependencies'])) {
      foreach ($info['dependencies'] as $dependency) {
        if ($dependency === 'media_entity' || $dependency === 'media_entity:media_entity') {
          $incompatible_modules['modules'][$module_name] = $module_name;
        }
      }
    }
  }

  // Disregard "media_entity_document" and "media_entity_image", once we will
  // uninstall them ourselves as part of the update hook.
  unset($incompatible_modules['providers']['media_entity_document']);
  unset($incompatible_modules['modules']['media_entity_document']);
  unset($incompatible_modules['providers']['media_entity_image']);
  unset($incompatible_modules['modules']['media_entity_image']);
  if (empty($incompatible_modules['providers'])) {
    unset($incompatible_modules['providers']);
  }
  if (empty($incompatible_modules['modules'])) {
    unset($incompatible_modules['modules']);
  }
  return $incompatible_modules;
}

/**
 * Helper function to rename config dependencies.
 *
 * @param string $dependency_type
 *   The type of the dependency, such as "module" or "config".
 * @param string $dependency_id
 *   The name of the dependency to be updated.
 * @param callable $map
 *   A callback to be passed to array_map() to actually perform the config name
 *   substitution.
 */
function _media_entity_fix_dependencies($dependency_type, $dependency_id, callable $map) {
  $dependents = \Drupal::service('config.manager')
    ->findConfigEntityDependents($dependency_type, [
    $dependency_id,
  ]);
  $key = 'dependencies.' . $dependency_type;
  foreach (array_keys($dependents) as $config) {
    $config = \Drupal::configFactory()
      ->getEditable($config);
    $dependencies = $config
      ->get($key);
    if (is_array($dependencies)) {
      $config
        ->set($key, array_map($map, $dependencies))
        ->save();
    }
  }
}

/**
 * Gets the names of all media bundles that use a particular type plugin.
 *
 * @param string $plugin_id
 *   The type plugin ID.
 *
 * @return string[]
 *   The media bundle IDs which use the specified plugin.
 */
function _media_entity_get_bundles_by_plugin($plugin_id) {
  $types = [];
  foreach (\Drupal::configFactory()
    ->listAll('media_entity.bundle') as $name) {
    if (\Drupal::config($name)
      ->get('type') == $plugin_id) {
      $types[] = explode('.', $name, 3)[2];
    }
  }
  return $types;
}

/**
 * Checks whether this site has image types with EXIF handling enabled.
 *
 * @return string[]
 *   The media bundle IDs which have the EXIF handling enabled, or an empty
 *   array if none have it so.
 */
function _media_entity_get_bundles_using_exif() {
  $bundles = [];
  foreach (_media_entity_get_bundles_by_plugin('image') as $bundle_name) {
    $gather_exif = \Drupal::config("media_entity.bundle.{$bundle_name}")
      ->get('type_configuration.gather_exif');
    if ($gather_exif) {
      $bundles[] = $bundle_name;
    }
  }
  return $bundles;
}

/**
 * Gets all media base field overrides.
 *
 * @return array[]
 *   The key is the field name where the override belongs to and the value is
 *   an array of all bundles the field is used in.
 */
function _media_entity_get_base_field_overrides() {
  $fields = [];
  $prefix = 'core.base_field_override.media.';
  foreach (\Drupal::configFactory()
    ->listAll($prefix) as $override) {
    list($bundle, $field) = explode('.', mb_substr($override, mb_strlen($prefix)));
    $fields[$field][] = $bundle;
  }
  return $fields;
}

/**
 * Implements hook_requirements().
 */
function media_entity_requirements($phase) {
  $requirements = [];
  if ($phase == 'update' && !_media_entity_check_entity_version()) {
    $requirements['entity'] = [
      'title' => t('Media entity'),
      'value' => t('Entity API missing'),
      'description' => t('<a href=":url">Entity API >= 8.x-1.0-alpha3</a> module is now a dependency and needs to be installed before running updates.', [
        ':url' => 'https://www.drupal.org/project/entity',
      ]),
      'severity' => _media_entity_check_entity_version(),
    ];
  }

  // Prevent this branch from being installed on new sites.
  if ($phase == 'install') {
    $requirements['media_entity_update_only'] = [
      'title' => t('Media entity'),
      'description' => t('This branch of Media Entity is intended for site upgrades only. Please use the 1.x branch or Drupal core >= 8.6.x if you are building a new site.'),
      'severity' => REQUIREMENT_ERROR,
    ];
  }
  if ($phase == 'update') {

    // Here we want to ensure that a series of requirements are met before
    // letting the DB updates continue. However, the batch processing of
    // hook_update_N() triggers this validation again during the update process.
    // Because of that, we want to make sure that these requirements checks are
    // only evaluated once, and we use a state variable for that.
    if (!\Drupal::state()
      ->get('media_entity_core_upgrade_started')) {
      $checks = \Drupal::service('media_entity.cli')
        ->validateDbUpdateRequirements();
      foreach ($checks['errors'] as $key => $error_msg) {
        $requirements['media_entity_upgrade_' . $key] = [
          'title' => t('Media Entity'),
          'value' => t('Please fix the error below and try again.'),
          'description' => $error_msg,
          'severity' => REQUIREMENT_ERROR,
        ];
      }
    }
  }
  if ($phase == 'runtime') {
    if (drupal_get_installed_schema_version('media_entity') < 8201) {
      $requirements['media_entity_update_status'] = [
        'title' => t('Media Entity'),
        'value' => t('DB updates for Media Entity pending.'),
        'description' => t('After updating the Media Entity code, you need to run the <a href=":update">database update script</a> as soon as possible.', [
          ':update' => Url::fromRoute('system.db_update')
            ->toString(),
        ]),
        'severity' => REQUIREMENT_WARNING,
      ];
    }
    else {
      $requirements['media_entity_update_status'] = [
        'title' => t('Media Entity'),
        'value' => t('DB updates for Media Entity were run.'),
        'description' => t('The Media Entity upgrade path was executed, you can now uninstall and remove the Media Entity module from the codebase.'),
        'severity' => REQUIREMENT_OK,
      ];
    }
  }
  return $requirements;
}

/**
 * Remove "type" base field.
 */
function media_entity_update_8001() {
  $fields = \Drupal::database()
    ->query('DESCRIBE {media_field_data}')
    ->fetchCol();
  if (in_array('type', $fields)) {
    \Drupal::database()
      ->update('media_field_data')
      ->fields([
      'type' => NULL,
    ])
      ->execute();
  }
  $manager = \Drupal::entityDefinitionUpdateManager();
  if ($field = $manager
    ->getFieldStorageDefinition('type', 'media')) {
    $manager
      ->uninstallFieldStorageDefinition($field);
  }
}

/**
 * Ensure media entity status value (defaulting to 1).
 */
function media_entity_update_8002() {

  // Ensure status values in 'media_field_data' table.
  if (\Drupal::database()
    ->schema()
    ->tableExists('media_field_data')) {
    \Drupal::database()
      ->update('media_field_data')
      ->fields([
      'status' => 1,
    ])
      ->condition('status', NULL, 'IS NULL')
      ->execute();
  }

  // Ensure status values in 'media_field_revision' table.
  if (\Drupal::database()
    ->schema()
    ->tableExists('media_field_revision')) {
    \Drupal::database()
      ->update('media_field_revision')
      ->fields([
      'status' => 1,
    ])
      ->condition('status', NULL, 'IS NULL')
      ->execute();
  }

  // Flush all caches.
  drupal_flush_all_caches();
}

/**
 * Ensure Entity API is installed.
 */
function media_entity_update_8003() {
  if (_media_entity_check_entity_version() === REQUIREMENT_ERROR) {
    throw new UpdateException('Entity API >= 8.x-1.0-alpha3 (drupal.org/project/entity) module is now a dependency and needs to be installed before running updates.');
  }
}

/**
 * Clears the module handler's hook implementation cache.
 */
function media_entity_update_8200() {
  \Drupal::moduleHandler()
    ->resetImplementations();
  \Drupal::service('plugin.cache_clearer')
    ->clearCachedDefinitions();

  // Update the installed entity type so that system_update_8501() works.
  $entity_type = \Drupal::entityTypeManager()
    ->getDefinition('media');
  \Drupal::entityDefinitionUpdateManager()
    ->updateEntityType($entity_type);
}

/**
 * Replace Media Entity with Media.
 */
function media_entity_update_8201() {
  \Drupal::state()
    ->set('media_entity_core_upgrade_started', TRUE);

  // When Media is installed, it assumes that it needs to create media bundles
  // and fields. Because this is an upgrade from Media Entity, that's not the
  // case. Track existing media types and fields, so that later when we delete
  // the auto-created ones, we don't throw the baby out with the bathwater.
  $preexisting_media_config = [];
  $prefixes = [
    'core.entity_form_display.media.',
    'core.entity_view_display.media.',
    'field.field.media.',
    'field.storage.media.',
    'media.type.',
  ];
  foreach ($prefixes as $prefix) {
    foreach (\Drupal::configFactory()
      ->listAll($prefix) as $name) {
      $preexisting_media_config[] = $name;
    }
  }
  $snapshots = _media_entity_snapshot_config([
    'core.entity_view_mode.media.full',
    'core.entity_form_display.media.file.default',
    'core.entity_form_display.media.image.default',
    'core.entity_view_display.media.file.default',
    'core.entity_view_display.media.image.default',
  ], TRUE);
  $install = [
    'media',
  ];

  // Install media_entity_generic if available. It stands to reason that this
  // module will only be available if you have at least one media type that uses
  // the generic plugin, since it has been split out into its own module and is
  // only requested if there are media bundles that use it.
  // See media_entity_requirements() and
  // \Drupal\media_entity\CliService::validateDbUpdateRequirements()/
  $module_data = system_rebuild_module_data();
  if (isset($module_data['media_entity_generic'])) {
    $install[] = 'media_entity_generic';
  }

  // EXIF image handling was dropped from the patch that moved ME + MEI into
  // core. Enable "Media Entity Image EXIF" if needed, which fills in that gap.
  // See media_entity_requirements() and
  // \Drupal\media_entity\CliService::validateDbUpdateRequirements()/
  $bundles_with_exif = _media_entity_get_bundles_using_exif();
  if (!empty($bundles_with_exif) && isset($module_data['media_entity_image_exif'])) {
    $install[] = 'media_entity_image_exif';
  }

  // The module installer rebuilds container after module install. All services
  // previously instantiated could have the wrong state. Do not use any services
  // via previously assign local variables.
  \Drupal::service('module_installer')
    ->install($install);
  $config_factory = \Drupal::configFactory();
  foreach ($snapshots as $name => $data) {
    $config_factory
      ->getEditable($name)
      ->setData($data)
      ->save(TRUE);
  }
  unset($snapshots);

  // Renames the revision fields in the media_revision table and ensures that
  // overrides of these fields are also changed properly.

  /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_definitions */
  $field_definitions = \Drupal::service('entity_field.manager')
    ->getFieldStorageDefinitions('media');
  $db = Database::getConnection()
    ->schema();
  $field_renames = [
    'revision_uid' => 'revision_user',
    'revision_timestamp' => 'revision_created',
    'revision_log' => 'revision_log_message',
  ];
  $field_overrides = _media_entity_get_base_field_overrides();
  foreach ($field_renames as $old_field => $new_field) {
    $field_columns = $field_definitions[$new_field]
      ->getColumns();
    $field_property_name = $field_definitions[$new_field]
      ->getMainPropertyName();
    $db
      ->changeField('media_revision', $old_field, $new_field, $field_columns[$field_property_name]);
    if (!empty($field_overrides[$old_field])) {
      foreach ($field_overrides[$old_field] as $bundle) {
        $config_factory
          ->getEditable("core.base_field_override.media.{$bundle}.{$old_field}")
          ->set('id', "media.{$bundle}.{$new_field}")
          ->set('field_name', $new_field)
          ->save(TRUE);
        $config_factory
          ->rename("core.base_field_override.media.{$bundle}.{$old_field}", "core.base_field_override.media.{$bundle}.{$new_field}");
      }
    }
  }

  // Delete file/image media types automatically created by core media and
  // associated fields.
  foreach ($prefixes as $prefix) {
    foreach ($config_factory
      ->listAll($prefix) as $name) {
      if (!in_array($name, $preexisting_media_config)) {

        // We should only do a proper entity delete on media types if we're not
        // going to rename an existing media_entity bundle to take their place
        // later.
        if ($prefix === 'media.type.') {
          $media_entity_bundle_name = preg_replace('/^media\\.type\\./', 'media_entity.bundle.', $name);
          if (!$config_factory
            ->get($media_entity_bundle_name)
            ->isNew()) {

            // Delete the media type directly from configuration to avoid
            // triggering bundle delete hooks.
            $config_factory
              ->getEditable($name)
              ->delete();
            continue;
          }
        }
        $entity = \Drupal::service('config.manager')
          ->loadConfigEntityByName($name);

        // Check to see if the configuration entity exists. It's possible that
        // previous deletes have deleted everything.
        if ($entity) {
          $entity
            ->delete();
        }
      }
    }
  }

  // Move all module dependencies on existing config entities from
  // "media_entity" to "media".
  _media_entity_fix_dependencies('module', 'media_entity', function ($module) {
    return $module === 'media_entity' ? 'media' : $module;
  });

  // Move all module dependencies on existing config entities from
  // "media_entity_document" to "media".
  _media_entity_fix_dependencies('module', 'media_entity_document', function ($module) {
    return $module === 'media_entity_document' ? 'media' : $module;
  });

  // Move all module dependencies on existing config entities from
  // "media_entity_image" to "media".
  _media_entity_fix_dependencies('module', 'media_entity_image', function ($module) {
    return $module === 'media_entity_image' ? 'media' : $module;
  });

  // Move media_entity.settings to media.settings. Note that we don't read and
  // save in bulk because the key "icon_base" moved to "icon_base_uri".
  $config_factory
    ->getEditable('media.settings')
    ->set('icon_base_uri', $config_factory
    ->get('media_entity.settings')
    ->get('icon_base'))
    ->set('standalone_url', TRUE)
    ->save(TRUE);
  $config_factory
    ->getEditable('media_entity.settings')
    ->delete();

  // Move all bundle configs to the new plugin namespace. This means moving all
  // "media_entity.bundle.*" to "media.type.*".
  foreach ($config_factory
    ->listAll('media_entity.bundle.') as $original_name) {
    $search = '/^media_entity\\.bundle\\./';
    $replace = 'media.type.';
    $new_name = preg_replace($search, $replace, $original_name);
    $config_factory
      ->rename($original_name, $new_name);
    $config = $config_factory
      ->getEditable($new_name);
    $source_id = $config
      ->get('type');
    $config
      ->set('source_configuration', $config
      ->get('type_configuration'))
      ->clear('type_configuration')
      ->set('source', $source_id == 'document' ? 'file' : $source_id)
      ->clear('type')
      ->save();
    _media_entity_fix_dependencies('config', $original_name, function ($bundle_id) use ($search, $replace) {
      return preg_replace($search, $replace, $bundle_id);
    });

    /** @var \Drupal\media\MediaTypeInterface $media_type */
    $media_type = \Drupal::entityTypeManager()
      ->getStorage('media_type')
      ->load($config
      ->get('id'));
    $media_source = $media_type
      ->getSource();
    $source_field = $media_source
      ->getSourceFieldDefinition($media_type);
    if (!$source_field) {
      $source_field = $media_source
        ->createSourceField($media_type);
      $source_field
        ->getFieldStorageDefinition()
        ->save();
      $source_field
        ->save();
      $media_type
        ->set('source_configuration', [
        'source_field' => $source_field
          ->getName(),
      ]);
    }
    $media_type
      ->save();
  }

  // Clear the old UUID map.
  \Drupal::keyValue(QueryFactory::CONFIG_LOOKUP_PREFIX . 'media_bundle')
    ->deleteAll();

  // Update any views that use the entity:media_bundle argument validator.
  _media_entity_update_views();

  // Update media action plugins.
  $old_new_action_id_map = [
    'media_delete_action' => 'entity:delete_action:media',
    'media_publish_action' => 'entity:publish_action:media',
    'media_unpublish_action' => 'entity:unpublish_action:media',
    'media_save_action' => 'entity:save_action:media',
  ];

  /** @var \Drupal\system\Entity\Action[] $actions */
  $actions = \Drupal::entityTypeManager()
    ->getStorage('action')
    ->loadMultiple();
  foreach ($actions as $action) {
    if (isset($old_new_action_id_map[$action
      ->getPlugin()
      ->getPluginId()])) {
      $action
        ->setPlugin($old_new_action_id_map[$action
        ->getPlugin()
        ->getPluginId()]);
      $action
        ->save();
    }
  }

  /** @var \Drupal\user\Entity\Role $role */
  foreach (Role::loadMultiple() as $role) {
    if ($role
      ->hasPermission('administer media bundles')) {
      $role
        ->revokePermission('administer media bundles')
        ->grantPermission('administer media types')
        ->save();
    }
  }

  // Disable media_entity_image, media_entity_document, and media_entity. They
  // are all superseded by core Media.
  if (isset($module_data['media_entity_image'])) {
    \Drupal::service('module_installer')
      ->uninstall([
      'media_entity_image',
    ]);
  }
  if (isset($module_data['media_entity_document'])) {
    \Drupal::service('module_installer')
      ->uninstall([
      'media_entity_document',
    ]);
  }
  \Drupal::service('module_installer')
    ->uninstall([
    'media_entity',
  ]);

  // @todo This can be removed when
  //   https://www.drupal.org/project/drupal/issues/3063734 lands.
  // Remove media_entity from the update schema list.
  register_shutdown_function(function () {
    \Drupal::keyValue('system.schema')
      ->delete('media_entity');
  });
}

/**
 * Updates any views that use the entity:media_bundle argument validator.
 */
function _media_entity_update_views() {
  $config_factory = \Drupal::configFactory();
  foreach ($config_factory
    ->listAll('views.view') as $name) {
    $view = $config_factory
      ->getEditable($name);
    $changed = FALSE;
    foreach ($view
      ->get('display') as $display_id => $display) {
      $key = "display.{$display_id}.display_options.arguments";

      // If there are no arguments, get() will return NULL, which is [] when
      // cast to an array.
      $arguments = (array) $view
        ->get($key);
      foreach ($arguments as $id => $argument) {
        if (isset($argument['validate']) && $argument['validate']['type'] == 'entity:media_bundle') {
          $view
            ->set("{$key}.{$id}.validate.type", 'entity:media_type');
          $changed = TRUE;
        }
      }
    }
    if ($changed) {
      $view
        ->save();
    }
  }
}

/**
 * Collects snapshots of config objects.
 *
 * @param string[] $names
 *   The names of the config objects to snapshot.
 * @param bool $delete
 *   (optional) Whether to delete the original config objects. Defaults to
 *   FALSE.
 *
 * @return array
 *   The config data, keyed by object name.
 */
function _media_entity_snapshot_config(array $names, $delete = FALSE) {
  $snapshots = [];
  foreach ($names as $name) {
    $config = \Drupal::configFactory()
      ->getEditable($name);
    if (!$config
      ->isNew()) {
      $snapshots[$name] = $config
        ->get();
      if ($delete) {
        $config
          ->delete();
      }
    }
  }
  return $snapshots;
}

/**
 * Implements hook_update_dependencies().
 */
function media_entity_update_dependencies() {
  $dependencies = [];

  // Ensure that media_entity_update_8200() comes before other updates. This
  // ensures that the fake media entity declared in
  // media_entity_entity_type_build() is available and code doesn't try to use
  // the old entity type that no longer exists. For example,
  // system_update_8402() and system_update_8501() both fail if
  // media_entity_update_8200() is not called first.
  foreach (update_get_update_list() as $module => $updates) {
    if ($module === 'media_entity') {
      continue;
    }
    if (!empty($updates['pending'])) {
      $first_update = key($updates['pending']);
      $dependencies[$module][$first_update]['media_entity'] = 8200;
    }
  }
  if (version_compare(\Drupal::VERSION, '8.8', '>=')) {

    // Ensure that system_update_8803() run before the media update, so that the
    // new path_alias module is installed.
    $dependencies['media_entity'][8201]['system'] = 8803;
  }
  else {

    // Ensure that system_update_8501() before the media update, so that the
    // new revision_default field is installed in the correct table.
    $dependencies['media_entity'][8201]['system'] = 8501;
  }
  return $dependencies;
}

Functions

Namesort descending Description
media_entity_requirements Implements hook_requirements().
media_entity_update_8001 Remove "type" base field.
media_entity_update_8002 Ensure media entity status value (defaulting to 1).
media_entity_update_8003 Ensure Entity API is installed.
media_entity_update_8200 Clears the module handler's hook implementation cache.
media_entity_update_8201 Replace Media Entity with Media.
media_entity_update_dependencies Implements hook_update_dependencies().
_media_entity_check_entity_version Checks if required version of the Entity API is installed.
_media_entity_fix_dependencies Helper function to rename config dependencies.
_media_entity_get_base_field_overrides Gets all media base field overrides.
_media_entity_get_bundles_by_plugin Gets the names of all media bundles that use a particular type plugin.
_media_entity_get_bundles_using_exif Checks whether this site has image types with EXIF handling enabled.
_media_entity_get_incompatible_modules Checks if all contrib modules depending on media_entity were updated.
_media_entity_snapshot_config Collects snapshots of config objects.
_media_entity_update_views Updates any views that use the entity:media_bundle argument validator.