Dumper.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <?php
  2. namespace Drupal\reinstall;
  3. use Drupal\Component\Utility\Unicode;
  4. use Drupal\Core\Entity\ContentEntityStorageInterface;
  5. use Drupal\Core\Entity\ContentEntityTypeInterface;
  6. use Drupal\Core\Entity\EntityTypeBundleInfo;
  7. use Drupal\Core\Entity\EntityTypeManagerInterface;
  8. use Drupal\Core\Session\AccountSwitcherInterface;
  9. use Drupal\Core\Session\UserSession;
  10. use Drupal\file\Entity\File;
  11. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  12. use Symfony\Component\Serializer\Serializer;
  13. use Symfony\Component\Yaml\Yaml;
  14. /**
  15. * Class Dumper provides export for content entities.
  16. *
  17. * @see \Drupal\reinstall\DumperEvents
  18. */
  19. class Dumper {
  20. const CONTENT_INTERFACE = 'Drupal\Core\Entity\ContentEntityInterface';
  21. /**
  22. * The path to the data files to import, relative to app.root.
  23. */
  24. const IMPORT_PATH = '../data';
  25. /**
  26. * The structure depth at which the YAML dump switched to inline format.
  27. */
  28. const INLINE_DEPTH = 5;
  29. /**
  30. * The account_switcher service.
  31. *
  32. * @var \Drupal\Core\Session\AccountSwitcherInterface
  33. */
  34. protected $accountSwitcher;
  35. /**
  36. * The entity_type.bundle_info service.
  37. *
  38. * @var \Drupal\Core\Entity\EntityTypeBundleInfo
  39. */
  40. protected $entityTypeBundleInfo;
  41. /**
  42. * The entity_type.manager service.
  43. *
  44. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  45. */
  46. protected $entityTypeManager;
  47. /**
  48. * The event_dispatcher service.
  49. *
  50. * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
  51. */
  52. protected $eventDispatcher;
  53. /**
  54. * The actual path from which to perform imports.
  55. *
  56. * Derived from @app_root and %rdcm.reinstall_path%.
  57. *
  58. * @var string
  59. */
  60. protected $importPath = self::IMPORT_PATH;
  61. /**
  62. * The app.root parameter service.
  63. *
  64. * @var string
  65. */
  66. protected $root;
  67. /**
  68. * The serializer service.
  69. *
  70. * @var \Symfony\Component\Serializer\Serializer
  71. */
  72. protected $serializer;
  73. /**
  74. * Dumper constructor.
  75. *
  76. * @param \Drupal\Core\Session\AccountSwitcherInterface $accountSwitcher
  77. * The account_switcher service.
  78. * @param \Drupal\Core\Entity\EntityTypeBundleInfo $bundleInfo
  79. * The entity_type.bundle_info service.
  80. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
  81. * The entity_type.manager service.
  82. * @param string $root
  83. * The value of the app.root "parameter service".
  84. * @param \Symfony\Component\Serializer\Serializer $serializer
  85. * The serializer service.
  86. * @param string $path
  87. * The import path.
  88. */
  89. public function __construct(
  90. AccountSwitcherInterface $accountSwitcher,
  91. EntityTypeBundleInfo $bundleInfo,
  92. EntityTypeManagerInterface $entityTypeManager,
  93. string $root,
  94. Serializer $serializer,
  95. EventDispatcherInterface $eventDispatcher,
  96. string $path
  97. ) {
  98. $this->accountSwitcher = $accountSwitcher;
  99. $this->entityTypeBundleInfo = $bundleInfo;
  100. $this->entityTypeManager = $entityTypeManager;
  101. $this->root = $root;
  102. $this->serializer = $serializer;
  103. $this->eventDispatcher = $eventDispatcher;
  104. $this->setPath($path);
  105. }
  106. /**
  107. * Gets a hash of the content entity definitions on the site.
  108. *
  109. * @param string $requestedTypeName
  110. * If specified, the types hash will only contain the key for that type.
  111. *
  112. * @return array
  113. * A machine-name-indexed hash of entity type definitions. If $typeName was
  114. * not specified, the definitions are returned for all entity types.
  115. */
  116. public function contentEntityTypes(string $requestedTypeName = NULL) {
  117. $definitions = $this->entityTypeManager->getDefinitions();
  118. $entityTypes = [];
  119. foreach ($definitions as $machine => $type) {
  120. $class = $type->getClass();
  121. $implements = class_implements($class);
  122. if (isset($implements[static::CONTENT_INTERFACE])) {
  123. $entityTypes[$machine] = $type;
  124. }
  125. }
  126. if (!empty($requestedTypeName)) {
  127. $entityTypes = [$requestedTypeName => $entityTypes[$requestedTypeName]];
  128. }
  129. return $entityTypes;
  130. }
  131. /**
  132. * Load entities for a given entity bundle.
  133. *
  134. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $type
  135. * The entity type object.
  136. * @param \Drupal\Core\Entity\ContentEntityStorageInterface $storage
  137. * The entity storage for the entity type.
  138. *
  139. * @return array
  140. * A hash of entities by id.
  141. */
  142. protected function loadMultiBundleEntities(ContentEntityTypeInterface $type,
  143. ContentEntityStorageInterface $storage
  144. ) {
  145. $bundleNames = array_keys($this->entityTypeBundleInfo->getBundleInfo($type->id()));
  146. $key = $type->getKey('bundle');
  147. $entities = [];
  148. foreach ($bundleNames as $bundleName) {
  149. $bundleEntities = $storage->loadByProperties([$key => $bundleName]);
  150. $entities[$bundleName] = $bundleEntities;
  151. }
  152. return $entities;
  153. }
  154. /**
  155. * Dump entities to YAML files.
  156. *
  157. * @param string $requestedTypeName
  158. * Optional. The name of the entity type for which to export entities. If
  159. * absent, all entities in all types are dumped.
  160. *
  161. * @see https://www.drupal.org/node/218104
  162. */
  163. public function dump($requestedTypeName = NULL) {
  164. $this->accountSwitcher->switchTo(new UserSession(['uid' => 1]));
  165. $entityTypes = $this->contentEntityTypes($requestedTypeName);
  166. foreach ($entityTypes as $entityTypeName => $entityType) {
  167. $storage = $this->entityTypeManager->getStorage($entityTypeName);
  168. $entities = $entityType->hasKey('bundle')
  169. ? $this->loadMultiBundleEntities($entityType, $storage)
  170. : [$entityTypeName => $storage->loadMultiple()];
  171. foreach ($entities as $bundleName => $bundleEntities) {
  172. // Allow adding data to entities before exporting, like term parents.
  173. $eventPre = new DumperEvent($storage, $bundleName, $bundleEntities);
  174. $this->eventDispatcher->dispatch(DumperEvents::PRE_DUMP, $eventPre);
  175. $this->dumpEntities($entityTypeName, $bundleName, $bundleEntities);
  176. // Allow extra work after exporting, like copying files.
  177. $eventPost = new DumperEvent($storage, $bundleName, $bundleEntities);
  178. $this->eventDispatcher->dispatch(DumperEvents::POST_DUMP, $eventPost);
  179. }
  180. }
  181. $this->accountSwitcher->switchBack();
  182. }
  183. /**
  184. * Generate import YAML for entities.
  185. *
  186. * @param string $entityTypeName
  187. * The entity type.
  188. * @param string $bundleName
  189. * The bundle name.
  190. * @param array $entities
  191. * The entities.
  192. */
  193. public function dumpEntities(string $entityTypeName, string $bundleName, array $entities) {
  194. $array = $this->toArray($entities);
  195. $path = $this->prepareDestination($entityTypeName, $bundleName);
  196. file_put_contents($path, Yaml::dump($array, static::INLINE_DEPTH, 2));
  197. }
  198. /**
  199. * Prepare the dump destination directory and return the file name within it.
  200. *
  201. * @param string $entityTypeName
  202. * @param string $bundleName
  203. *
  204. * @return string
  205. */
  206. protected function prepareDestination(string $entityTypeName, string $bundleName): string {
  207. $importPath = $this->importPath;
  208. $dir = "$importPath/$entityTypeName";
  209. if (!file_exists($dir)) {
  210. mkdir($dir, 0777, TRUE);
  211. }
  212. $path = "${dir}/${bundleName}.yml";
  213. return $path;
  214. }
  215. /**
  216. * Store the absolute path to the import directory.
  217. *
  218. * @param string $path
  219. * The Drupal-root-relative import path.
  220. *
  221. * @throws \InvalidArgumentException
  222. * If the directory does not exist.
  223. */
  224. public function setPath(string $path) {
  225. $completePath = $this->root . '/' . $path;
  226. $real = realpath($completePath);
  227. if (!is_dir($real)) {
  228. drupal_set_message("Non-existent base dump directory: $completePath.", "error");
  229. throw new \InvalidArgumentException("Non-existent base dump directory: $completePath.");
  230. return;
  231. }
  232. $this->importPath = $real;
  233. }
  234. /**
  235. * Like NormalizerInterface::normalize(), but for an array.
  236. *
  237. * @param array $entities
  238. *
  239. * @return mixed
  240. */
  241. protected function toArray(array $entities): array {
  242. $json_options = [];
  243. $json = $this->serializer->serialize($entities, 'json', $json_options);
  244. $hash = json_decode($json, TRUE);
  245. return $hash;
  246. }
  247. }