accountSwitcher = $accountSwitcher; $this->entityTypeBundleInfo = $bundleInfo; $this->entityTypeManager = $entityTypeManager; $this->root = $root; $this->serializer = $serializer; $this->setPath($importPath); } /** * 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; } /** * 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 $requestedTypeName * Optional. The name of the entity type for which to export entities. If * absent, all entities in all types are dumped. * * @see https://www.drupal.org/node/218104 */ public function dump($requestedTypeName = NULL) { $this->accountSwitcher->switchTo(new UserSession(['uid' => 1])); $entityTypes = $this->contentEntityTypes($requestedTypeName); foreach ($entityTypes as $entityTypeName => $entityType) { $storage = $this->entityTypeManager->getStorage($entityTypeName); $entities = $entityType->hasKey('bundle') ? $this->loadMultiBundleEntities($entityType, $storage) : [$entityTypeName => $storage->loadMultiple()]; foreach ($entities as $bundleName => $bundleEntities) { $this->dumpEntities($bundleEntities, $entityTypeName, $bundleName); if ($entityTypeName === 'file') { $this->dumpFiles($bundleEntities, $bundleName); } } } $this->accountSwitcher->switchBack(); } /** * Generate import YAML for entities. * * @param array $entities * The entities. * @param string $entity_type * The entity type. * @param string $bundle * The bundle name. */ public function dumpEntities(array $entities, string $entity_type, string $bundle = NULL) { if (!$bundle) { $bundle = $entity_type; } $json_options = []; $json = $this->serializer->serialize($entities, 'json', $json_options); $hash = json_decode($json, TRUE); $importPath = $this->importPath; $dir = "$importPath/$entity_type"; if (!file_exists($dir)) { mkdir($dir, 0777, TRUE); } $path = "${dir}/${bundle}.yml"; file_put_contents($path, Yaml::dump($hash, static::INLINE_DEPTH, 2)); } public function dumpFiles(array $files, string $bundle) { $importPath = $this->importPath; $dir = "$importPath/file"; $usedNamespaces = array_keys(array_reduce($files, [$this, 'namespacesReducer'], [])); $lists = []; foreach ($usedNamespaces as $ns) { // XXX Consider using \0 to support xargs: file names MAY contain spaces. $path = "$dir/$ns.list.txt"; $nsDir = "$dir/$ns"; if (!is_dir($nsDir)) { echo "Creating $nsDir\n"; mkdir($nsDir, 0777, TRUE); } // fopen() is in text mode by default. $lists[$ns] = [ 'dir' => $nsDir, 'handle' => fopen($path, 'w'), ]; } /** * @var int $fid * @var \Drupal\file\Entity\File $file */ foreach ($files as $fid => $file) { $uri = $file->getFileUri(); $target = file_uri_target($uri); fputs($lists[$ns]['handle'], $target . "\n"); $dest = $lists[$ns]['dir'] . '/' . $target; $dir = dirname($dest); if (!is_dir($dir)) { mkdir($dir, 0777, TRUE); } file_unmanaged_copy($uri, $dest, FILE_EXISTS_REPLACE); } foreach ($lists as $list) { fclose($list['handle']); } } /** * array_reduce() callback to collect namespaces from file entities. * * @param string[] $accu * @param \Drupal\file\Entity\File $fileItem * * @return string[] * * @see \Drupal\reinstall\Dumper::dumpFiles() */ protected function namespacesReducer(array $accu, File $fileItem) { $uri = $fileItem->getFileUri(); // Plain filenames without a namespace. Should not happen, but... if (FALSE === ($len = Unicode::strpos($uri, '://'))) { return $accu; }; $namespace = Unicode::substr($uri, 0, $len); $accu[$namespace] = TRUE; return $accu; } 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."); return; } $this->importPath = $real; } }