|
@@ -1,425 +0,0 @@
|
|
|
-<?php
|
|
|
-
|
|
|
-namespace Drupal\reinstall;
|
|
|
-
|
|
|
-use Drupal\Core\Entity\ContentEntityStorageInterface;
|
|
|
-use Drupal\Core\Entity\ContentEntityTypeInterface;
|
|
|
-use Drupal\Core\Entity\EntityStorageInterface;
|
|
|
-use Drupal\Core\Entity\EntityTypeBundleInfo;
|
|
|
-use Drupal\Core\Entity\EntityTypeInterface;
|
|
|
-use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
|
-use Drupal\Core\Session\AccountSwitcherInterface;
|
|
|
-use Drupal\Core\Session\UserSession;
|
|
|
-use Psr\Log\LoggerInterface;
|
|
|
-use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
|
-use Symfony\Component\Serializer\Serializer;
|
|
|
-use Symfony\Component\Yaml\Yaml;
|
|
|
-
|
|
|
-/**
|
|
|
- * Class Dumper provides export for content entities.
|
|
|
- *
|
|
|
- * @see \Drupal\reinstall\ReinstallEvents
|
|
|
- */
|
|
|
-class Dumper {
|
|
|
-
|
|
|
- /**
|
|
|
- * Should probably be a service parameter in the future.
|
|
|
- */
|
|
|
- const BATCH_SIZE = 50;
|
|
|
-
|
|
|
- /**
|
|
|
- * The interface used to ensure the dump request is for content entities.
|
|
|
- */
|
|
|
- const CONTENT_INTERFACE = 'Drupal\Core\Entity\ContentEntityInterface';
|
|
|
-
|
|
|
- /**
|
|
|
- * The path to the data files to import, relative to app.root.
|
|
|
- */
|
|
|
- const IMPORT_PATH = '../data';
|
|
|
-
|
|
|
- /**
|
|
|
- * The structure depth at which the YAML dump switched to inline format.
|
|
|
- */
|
|
|
- const INLINE_DEPTH = 5;
|
|
|
-
|
|
|
- /**
|
|
|
- * The account_switcher service.
|
|
|
- *
|
|
|
- * @var \Drupal\Core\Session\AccountSwitcherInterface
|
|
|
- */
|
|
|
- protected $accountSwitcher;
|
|
|
-
|
|
|
- /**
|
|
|
- * The entity_type.bundle_info service.
|
|
|
- *
|
|
|
- * @var \Drupal\Core\Entity\EntityTypeBundleInfo
|
|
|
- */
|
|
|
-
|
|
|
- protected $entityTypeBundleInfo;
|
|
|
-
|
|
|
- /**
|
|
|
- * The entity_type.manager service.
|
|
|
- *
|
|
|
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
|
|
- */
|
|
|
- protected $entityTypeManager;
|
|
|
-
|
|
|
- /**
|
|
|
- * The event_dispatcher service.
|
|
|
- *
|
|
|
- * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
|
|
- */
|
|
|
- protected $eventDispatcher;
|
|
|
-
|
|
|
- /**
|
|
|
- * The actual path from which to perform imports.
|
|
|
- *
|
|
|
- * Derived from @app_root and %rdcm.reinstall_path%.
|
|
|
- *
|
|
|
- * @var string
|
|
|
- */
|
|
|
- protected $importPath = self::IMPORT_PATH;
|
|
|
-
|
|
|
- /**
|
|
|
- * The reinstall logger channel service.
|
|
|
- *
|
|
|
- * @var \Psr\Log\LoggerInterface
|
|
|
- */
|
|
|
- protected $logger;
|
|
|
-
|
|
|
- /**
|
|
|
- * The app.root parameter service.
|
|
|
- *
|
|
|
- * @var string
|
|
|
- */
|
|
|
- protected $root;
|
|
|
-
|
|
|
- /**
|
|
|
- * The serializer service.
|
|
|
- *
|
|
|
- * @var \Symfony\Component\Serializer\Serializer
|
|
|
- */
|
|
|
- protected $serializer;
|
|
|
-
|
|
|
- /**
|
|
|
- * Dumper constructor.
|
|
|
- *
|
|
|
- * @param \Drupal\Core\Session\AccountSwitcherInterface $accountSwitcher
|
|
|
- * The account_switcher service.
|
|
|
- * @param \Drupal\Core\Entity\EntityTypeBundleInfo $bundleInfo
|
|
|
- * The entity_type.bundle_info service.
|
|
|
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
|
|
- * The entity_type.manager service.
|
|
|
- * @param string $root
|
|
|
- * The value of the app.root "parameter service".
|
|
|
- * @param \Symfony\Component\Serializer\Serializer $serializer
|
|
|
- * The serializer service.
|
|
|
- * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
|
|
|
- * The event_dispatcher service.
|
|
|
- * @param string $path
|
|
|
- * The import path.
|
|
|
- * @param \Psr\Log\LoggerInterface $logger
|
|
|
- * The reinstall logger channel service.
|
|
|
- */
|
|
|
- public function __construct(
|
|
|
- AccountSwitcherInterface $accountSwitcher,
|
|
|
- EntityTypeBundleInfo $bundleInfo,
|
|
|
- EntityTypeManagerInterface $entityTypeManager,
|
|
|
- string $root,
|
|
|
- Serializer $serializer,
|
|
|
- EventDispatcherInterface $eventDispatcher,
|
|
|
- string $path,
|
|
|
- LoggerInterface $logger
|
|
|
- ) {
|
|
|
- $this->accountSwitcher = $accountSwitcher;
|
|
|
- $this->entityTypeBundleInfo = $bundleInfo;
|
|
|
- $this->entityTypeManager = $entityTypeManager;
|
|
|
- $this->logger = $logger;
|
|
|
- $this->root = $root;
|
|
|
- $this->serializer = $serializer;
|
|
|
- $this->eventDispatcher = $eventDispatcher;
|
|
|
-
|
|
|
- $this->setPath($path);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Gets a hash of the content entity definitions on the site.
|
|
|
- *
|
|
|
- * @param string $requestedTypeName
|
|
|
- * If specified, the types hash will only contain the key for that type.
|
|
|
- *
|
|
|
- * @return array
|
|
|
- * A machine-name-indexed hash of entity type definitions. If $typeName was
|
|
|
- * not specified, the definitions are returned for all entity types.
|
|
|
- */
|
|
|
- public function contentEntityTypes(string $requestedTypeName = NULL) {
|
|
|
- $definitions = $this->entityTypeManager->getDefinitions();
|
|
|
- $entityTypes = [];
|
|
|
- foreach ($definitions as $machine => $type) {
|
|
|
- $class = $type->getClass();
|
|
|
- $implements = class_implements($class);
|
|
|
- if (isset($implements[static::CONTENT_INTERFACE])) {
|
|
|
- $entityTypes[$machine] = $type;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!empty($requestedTypeName)) {
|
|
|
- $entityTypes = [$requestedTypeName => $entityTypes[$requestedTypeName]];
|
|
|
- }
|
|
|
-
|
|
|
- return $entityTypes;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Dump the entities in the selected bundle of the entity type.
|
|
|
- *
|
|
|
- * @param string $typeName
|
|
|
- * The name of the entity type from which to dump a bundle.
|
|
|
- * @param \Drupal\Core\Entity\EntityTypeInterface $entityType
|
|
|
- * The entity type instance.
|
|
|
- * @param string $bundle
|
|
|
- * The name of the bundle to dump. Assumed to be valid.
|
|
|
- *
|
|
|
- * @see \Drupal\reinstall\Dumper::validateBundles()
|
|
|
- */
|
|
|
- protected function dumpEntitiesBundle($typeName, EntityTypeInterface $entityType, string $bundle) {
|
|
|
- $t0 = microtime(TRUE);
|
|
|
- $storage = $this->entityTypeManager->getStorage($typeName);
|
|
|
- $bundleKey = $this->entityTypeManager->getDefinition($typeName)->getKey('bundle');
|
|
|
- $countQuery = $storage->getQuery();
|
|
|
- if ($bundleKey) {
|
|
|
- $countQuery = $countQuery->condition($bundleKey, $bundle);
|
|
|
- }
|
|
|
- $count = $countQuery
|
|
|
- ->count()
|
|
|
- ->execute();
|
|
|
-
|
|
|
- $query = $storage->getQuery();
|
|
|
- if ($bundleKey) {
|
|
|
- $query = $query->condition($bundleKey, $bundle);
|
|
|
- }
|
|
|
- $ids = $query->execute();
|
|
|
- $chunks = array_chunk($ids, static::BATCH_SIZE);
|
|
|
-
|
|
|
- $path = $this->prepareDestination($typeName, $bundle);
|
|
|
- $fp = fopen($path, "w");
|
|
|
-
|
|
|
- foreach ($chunks as $chunk) {
|
|
|
- $this->dumpEntitiesChunk($fp, $typeName, $bundle, $storage, $chunk);
|
|
|
- }
|
|
|
-
|
|
|
- // Ensure files always contain at least an empty array.
|
|
|
- if (empty($chunks)) {
|
|
|
- fwrite($fp, '{ }');
|
|
|
- }
|
|
|
-
|
|
|
- fclose($fp);
|
|
|
- $t1 = microtime(TRUE);
|
|
|
-
|
|
|
- $this->logger->info('Dumped @count entities for @type/@bundle in @sec seconds.', [
|
|
|
- '@count' => $count,
|
|
|
- '@type' => $typeName,
|
|
|
- '@bundle' => $bundle,
|
|
|
- '@sec' => sprintf('%.1f', $t1 - $t0),
|
|
|
- ]);
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Dump a chunk of entities from a common bundle in an existing opened file.
|
|
|
- *
|
|
|
- * @param resource $fp
|
|
|
- * A file pointer to which to write.
|
|
|
- * @param string $typeName
|
|
|
- * The name of the entity type from which to dump a chunk of entities.
|
|
|
- * @param string $bundle
|
|
|
- * The bundle name.
|
|
|
- * @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
|
|
- * The entity storage for the entity type.
|
|
|
- * @param array $chunk
|
|
|
- * An array of entities to dump.
|
|
|
- */
|
|
|
- protected function dumpEntitiesChunk(
|
|
|
- $fp,
|
|
|
- string $typeName,
|
|
|
- string $bundle,
|
|
|
- EntityStorageInterface $storage,
|
|
|
- array $chunk
|
|
|
- ) {
|
|
|
- $entities = $storage->loadMultiple($chunk);
|
|
|
-
|
|
|
- // Allow adding data to entities before exporting, like term parents.
|
|
|
- $eventPre = new DumperEvent($storage, $bundle, $entities);
|
|
|
- $this->eventDispatcher->dispatch(ReinstallEvents::PRE_DUMP, $eventPre);
|
|
|
-
|
|
|
- $this->dumpEntities($fp, $typeName, $bundle, $entities);
|
|
|
-
|
|
|
- // Allow extra work after exporting, like copying files.
|
|
|
- $eventPost = new DumperEvent($storage, $bundle, $entities);
|
|
|
- $this->eventDispatcher->dispatch(ReinstallEvents::POST_DUMP, $eventPost);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Load entities for a given entity bundle.
|
|
|
- *
|
|
|
- * @param \Drupal\Core\Entity\ContentEntityTypeInterface $type
|
|
|
- * The entity type object.
|
|
|
- * @param \Drupal\Core\Entity\ContentEntityStorageInterface $storage
|
|
|
- * The entity storage for the entity type.
|
|
|
- *
|
|
|
- * @return array
|
|
|
- * A hash of entities by id.
|
|
|
- */
|
|
|
- protected function loadMultiBundleEntities(ContentEntityTypeInterface $type,
|
|
|
- ContentEntityStorageInterface $storage
|
|
|
- ) {
|
|
|
- $bundleNames = array_keys($this->entityTypeBundleInfo->getBundleInfo($type->id()));
|
|
|
- $key = $type->getKey('bundle');
|
|
|
-
|
|
|
- $entities = [];
|
|
|
- foreach ($bundleNames as $bundleName) {
|
|
|
- $bundleEntities = $storage->loadByProperties([$key => $bundleName]);
|
|
|
- $entities[$bundleName] = $bundleEntities;
|
|
|
- }
|
|
|
-
|
|
|
- return $entities;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Dump entities to YAML files.
|
|
|
- *
|
|
|
- * @param string $typeName
|
|
|
- * The name of the entity type for which to export entities.
|
|
|
- * @param array $bundles
|
|
|
- * Optional. An array of bundles to export. Only used if an entity type is
|
|
|
- * specified, since $requestedTypeName is itself optional.
|
|
|
- *
|
|
|
- * @see https://www.drupal.org/node/218104
|
|
|
- */
|
|
|
- public function dump($typeName, array $bundles = []) {
|
|
|
- $this->accountSwitcher->switchTo(new UserSession(['uid' => 1]));
|
|
|
-
|
|
|
- $entityType = current($this->contentEntityTypes($typeName));
|
|
|
- $validBundles = $this->validateBundles($typeName, $bundles);
|
|
|
-
|
|
|
- foreach ($validBundles as $bundle) {
|
|
|
- $this->dumpEntitiesBundle($typeName, $entityType, $bundle);
|
|
|
- }
|
|
|
-
|
|
|
- $this->accountSwitcher->switchBack();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Generate import YAML for entities.
|
|
|
- *
|
|
|
- * @param resource $fp
|
|
|
- * The file into which to write.
|
|
|
- * @param string $entityTypeName
|
|
|
- * The entity type.
|
|
|
- * @param string $bundleName
|
|
|
- * The bundle name.
|
|
|
- * @param array $entities
|
|
|
- * The entities.
|
|
|
- */
|
|
|
- public function dumpEntities($fp, string $entityTypeName, string $bundleName, array $entities) {
|
|
|
- $array = $this->toArray($entities);
|
|
|
- fwrite($fp, Yaml::dump($array, static::INLINE_DEPTH, 2));
|
|
|
- fflush($fp);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Prepare the dump destination directory and return the file name within it.
|
|
|
- *
|
|
|
- * @param string $entityTypeName
|
|
|
- * The type of the entities to dump.
|
|
|
- * @param string $bundleName
|
|
|
- * The bundle of the entities to dump.
|
|
|
- *
|
|
|
- * @return string
|
|
|
- * The path of the dump file.
|
|
|
- */
|
|
|
- protected function prepareDestination(string $entityTypeName, string $bundleName): string {
|
|
|
- $importPath = $this->importPath;
|
|
|
- $dir = "$importPath/$entityTypeName";
|
|
|
- if (!file_exists($dir)) {
|
|
|
- mkdir($dir, 0777, TRUE);
|
|
|
- }
|
|
|
-
|
|
|
- $path = "${dir}/${bundleName}.yml";
|
|
|
- return $path;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Store the absolute path to the import directory.
|
|
|
- *
|
|
|
- * @param string $path
|
|
|
- *
|
|
|
- * The Drupal-root-relative import path.
|
|
|
- *
|
|
|
- * @throws \InvalidArgumentException
|
|
|
- * If the directory does not exist.
|
|
|
- */
|
|
|
- public function setPath(string $path) {
|
|
|
- $completePath = $this->root . '/' . $path;
|
|
|
- $real = realpath($completePath);
|
|
|
- if (!is_dir($real)) {
|
|
|
- drupal_set_message("Non-existent base dump directory: $completePath.", "error");
|
|
|
- throw new \InvalidArgumentException("Non-existent base dump directory: $completePath.");
|
|
|
- }
|
|
|
-
|
|
|
- $this->importPath = $real;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Like NormalizerInterface::normalize(), but for an array.
|
|
|
- *
|
|
|
- * @param array $entities
|
|
|
- * The entities to convert to arrays.
|
|
|
- *
|
|
|
- * @return mixed
|
|
|
- * The array representing the entities.
|
|
|
- */
|
|
|
- protected function toArray(array $entities): array {
|
|
|
- $json_options = [];
|
|
|
- $json = $this->serializer->serialize($entities, 'json', $json_options);
|
|
|
- $hash = json_decode($json, TRUE);
|
|
|
-
|
|
|
- return $hash;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Deduplicate the bundles list and remove invalid bundle names.
|
|
|
- *
|
|
|
- * @param string $entityTypeName
|
|
|
- * The name of the entity type for which to validate bundle names.
|
|
|
- * @param array $bundles
|
|
|
- * Bundle names to be validated.
|
|
|
- *
|
|
|
- * @return array
|
|
|
- * Valid bundles to dump.
|
|
|
- */
|
|
|
- protected function validateBundles(string $entityTypeName, array $bundles) {
|
|
|
- sort($bundles);
|
|
|
- $bundleInfo = $this->entityTypeBundleInfo->getBundleInfo($entityTypeName);
|
|
|
-
|
|
|
- // Note: array_flip will ensure requested bundles are only asked once.
|
|
|
- $flippedBundles = array_flip($bundles);
|
|
|
-
|
|
|
- $validFlippedBundles = array_intersect_key($flippedBundles, $bundleInfo);
|
|
|
- $uniqueValidBundles = array_flip($validFlippedBundles);
|
|
|
-
|
|
|
- sort($uniqueValidBundles);
|
|
|
- if ($bundles !== $uniqueValidBundles) {
|
|
|
- throw new \InvalidArgumentException(
|
|
|
- "\nRequested bundles: " . implode(', ', $bundles)
|
|
|
- . "\nValid bundles: " . implode(', ', $uniqueValidBundles) . "\n");
|
|
|
- }
|
|
|
-
|
|
|
- if (empty($uniqueValidBundles)) {
|
|
|
- $uniqueValidBundles = array_keys($bundleInfo);
|
|
|
- }
|
|
|
-
|
|
|
- return $uniqueValidBundles;
|
|
|
- }
|
|
|
-
|
|
|
-}
|