Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / modules / migrate_drupal / src / Plugin / migrate / source / ContentEntity.php
diff --git a/web/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntity.php b/web/core/modules/migrate_drupal/src/Plugin/migrate/source/ContentEntity.php
new file mode 100644 (file)
index 0000000..1219510
--- /dev/null
@@ -0,0 +1,294 @@
+<?php
+
+namespace Drupal\migrate_drupal\Plugin\migrate\source;
+
+use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
+use Drupal\migrate\Plugin\MigrationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Source plugin to get content entities from the current version of Drupal.
+ *
+ * This plugin uses the Entity API to export entity data. If the source entity
+ * type has custom field storage fields or computed fields, this class will need
+ * to be extended and the new class will need to load/calculate the values for
+ * those fields.
+ *
+ * Available configuration keys:
+ * - entity_type: The entity type ID of the entities being exported. This is
+ *   calculated dynamically by the deriver so it is only needed if the deriver
+ *   is not utilized, i.e., a custom source plugin.
+ * - bundle: (optional) If the entity type is bundleable, only return entities
+ *   of this bundle.
+ * - include_translations: (optional) Indicates if the entity translations
+ *   should be included, defaults to TRUE.
+ *
+ * Examples:
+ *
+ * This will return all nodes, from every bundle and every translation. It does
+ * not return all revisions, just the default one.
+ * @code
+ * source:
+ *   plugin: content_entity:node
+ * @endcode
+ *
+ * This will only return nodes of type 'article' in their default language.
+ * @code
+ * source:
+ *   plugin: content_entity:node
+ *   bundle: article
+ *   include_translations: false
+ * @endcode
+ *
+ * @MigrateSource(
+ *   id = "content_entity",
+ *   source_module = "migrate_drupal",
+ *   deriver = "\Drupal\migrate_drupal\Plugin\migrate\source\ContentEntityDeriver",
+ * )
+ */
+class ContentEntity extends SourcePluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * The entity type bundle info service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $entityTypeBundleInfo;
+
+  /**
+   * The entity type definition.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface
+   */
+  protected $entityType;
+
+  /**
+   * The plugin's default configuration.
+   *
+   * @var array
+   */
+  protected $defaultConfiguration = [
+    'bundle' => NULL,
+    'include_translations' => TRUE,
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
+    if (empty($plugin_definition['entity_type'])) {
+      throw new InvalidPluginDefinitionException($plugin_id, 'Missing required "entity_type" definition.');
+    }
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityFieldManager = $entity_field_manager;
+    $this->entityTypeBundleInfo = $entity_type_bundle_info;
+    $this->entityType = $this->entityTypeManager->getDefinition($plugin_definition['entity_type']);
+    if (!$this->entityType instanceof ContentEntityTypeInterface) {
+      throw new InvalidPluginDefinitionException($plugin_id, sprintf('The entity type (%s) is not supported. The "content_entity" source plugin only supports content entities.', $plugin_definition['entity_type']));
+    }
+    if (!empty($configuration['bundle'])) {
+      if (!$this->entityType->hasKey('bundle')) {
+        throw new \InvalidArgumentException(sprintf('A bundle was provided but the entity type (%s) is not bundleable.', $plugin_definition['entity_type']));
+      }
+      $bundle_info = array_keys($this->entityTypeBundleInfo->getBundleInfo($this->entityType->id()));
+      if (!in_array($configuration['bundle'], $bundle_info, TRUE)) {
+        throw new \InvalidArgumentException(sprintf('The provided bundle (%s) is not valid for the (%s) entity type.', $configuration['bundle'], $plugin_definition['entity_type']));
+      }
+    }
+    parent::__construct($configuration + $this->defaultConfiguration, $plugin_id, $plugin_definition, $migration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $migration,
+      $container->get('entity_type.manager'),
+      $container->get('entity_field.manager'),
+      $container->get('entity_type.bundle.info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return (string) $this->entityType->getPluralLabel();
+  }
+
+  /**
+   * Initializes the iterator with the source data.
+   *
+   * @return \Generator
+   *   A data generator for this source.
+   */
+  protected function initializeIterator() {
+    $ids = $this->query()->execute();
+    return $this->yieldEntities($ids);
+  }
+
+  /**
+   * Loads and yields entities, one at a time.
+   *
+   * @param array $ids
+   *   The entity IDs.
+   *
+   * @return \Generator
+   *   An iterable of the loaded entities.
+   */
+  protected function yieldEntities(array $ids) {
+    $storage = $this->entityTypeManager
+      ->getStorage($this->entityType->id());
+    foreach ($ids as $id) {
+      /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+      $entity = $storage->load($id);
+      yield $this->toArray($entity);
+      if ($this->configuration['include_translations']) {
+        foreach ($entity->getTranslationLanguages(FALSE) as $language) {
+          yield $this->toArray($entity->getTranslation($language->getId()));
+        }
+      }
+    }
+  }
+
+  /**
+   * Converts an entity to an array.
+   *
+   * Makes all IDs into flat values. All other values are returned as per
+   * $entity->toArray(), which is a nested array.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to convert.
+   *
+   * @return array
+   *   The entity, represented as an array.
+   */
+  protected function toArray(ContentEntityInterface $entity) {
+    $return = $entity->toArray();
+    // This is necessary because the IDs must be flat. They cannot be nested for
+    // the ID map.
+    foreach (array_keys($this->getIds()) as $id) {
+      /** @var \Drupal\Core\TypedData\Plugin\DataType\ItemList $value */
+      $value = $entity->get($id);
+      // Force the IDs on top of the previous values.
+      $return[$id] = $value->first()->getString();
+    }
+    return $return;
+  }
+
+  /**
+   * Query to retrieve the entities.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryInterface
+   *   The query.
+   */
+  public function query() {
+    $query = $this->entityTypeManager
+      ->getStorage($this->entityType->id())
+      ->getQuery()
+      ->accessCheck(FALSE);
+    if (!empty($this->configuration['bundle'])) {
+      $query->condition($this->entityType->getKey('bundle'), $this->configuration['bundle']);
+    }
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count($refresh = FALSE) {
+    // If no translations are included, then a simple query is possible.
+    if (!$this->configuration['include_translations']) {
+      return parent::count($refresh);
+    }
+    // @TODO: Determine a better way to retrieve a valid count for translations.
+    // https://www.drupal.org/project/drupal/issues/2937166
+    return -1;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doCount() {
+    return $this->query()->count()->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    // Retrieving fields from a non-fieldable content entity will throw a
+    // LogicException. Return an empty list of fields instead.
+    if (!$this->entityType->entityClassImplements('Drupal\Core\Entity\FieldableEntityInterface')) {
+      return [];
+    }
+    $field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
+    if (!empty($this->configuration['bundle'])) {
+      $field_definitions += $this->entityFieldManager->getFieldDefinitions($this->entityType->id(), $this->configuration['bundle']);
+    }
+    $fields = array_map(function ($definition) {
+      return (string) $definition->getLabel();
+    }, $field_definitions);
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $id_key = $this->entityType->getKey('id');
+    $ids[$id_key] = $this->getDefinitionFromEntity($id_key);
+    if ($this->entityType->isTranslatable()) {
+      $langcode_key = $this->entityType->getKey('langcode');
+      $ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
+    }
+    return $ids;
+  }
+
+  /**
+   * Gets the field definition from a specific entity base field.
+   *
+   * @param string $key
+   *   The field ID key.
+   *
+   * @return array
+   *   An associative array with a structure that contains the field type, keyed
+   *   as 'type', together with field storage settings as they are returned by
+   *   FieldStorageDefinitionInterface::getSettings().
+   *
+   * @see \Drupal\migrate\Plugin\migrate\destination\EntityContentBase::getDefinitionFromEntity()
+   */
+  protected function getDefinitionFromEntity($key) {
+    /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
+    $field_definition = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id())[$key];
+    return [
+      'type' => $field_definition->getType(),
+    ] + $field_definition->getSettings();
+  }
+
+}