123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- <?php
- namespace Drupal\reinstall;
- use Drupal\Component\Utility\Unicode;
- use Drupal\Core\Entity\ContentEntityStorageInterface;
- use Drupal\Core\Entity\ContentEntityTypeInterface;
- use Drupal\Core\Entity\EntityTypeBundleInfo;
- use Drupal\Core\Entity\EntityTypeManagerInterface;
- use Drupal\Core\Session\AccountSwitcherInterface;
- use Drupal\Core\Session\UserSession;
- use Drupal\file\Entity\File;
- use Symfony\Component\Serializer\Serializer;
- use Symfony\Component\Yaml\Yaml;
- /**
- * Class Dumper provides export for content entities.
- */
- class Dumper {
- 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 actual path from which to perform imports.
- *
- * Derived from @app_root and %rdcm.reinstall_path%.
- *
- * @var string
- */
- protected $importPath = self::IMPORT_PATH;
- /**
- * 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.
- */
- public function __construct(
- AccountSwitcherInterface $accountSwitcher,
- EntityTypeBundleInfo $bundleInfo,
- EntityTypeManagerInterface $entityTypeManager,
- string $root,
- Serializer $serializer,
- string $importPath
- ) {
- $this->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;
- }
- }
|