diff --git a/drush/default_content.drush.inc b/drush/default_content.drush.inc index 6c4ace0..c7cac2e 100644 --- a/drush/default_content.drush.inc +++ b/drush/default_content.drush.inc @@ -4,6 +4,8 @@ * @file * Drush integration for the default_content module. */ +use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityTypeInterface; /** * Implements hook_drush_command(). @@ -41,6 +43,24 @@ function default_content_drush_command() { 'aliases' => ['dcem'], 'required-arguments' => 1, ]; + $items['default-content-export-all'] = [ + 'description' => dt('Exports all content from any appropriate entity types.'), + 'options' => [ + 'folder' => dt('Folder to export to, entities are grouped by entity type into directories.'), + 'exclude' => [ + 'description' => dt('Entity types and/or bundles to exclude'), + 'example-value' => 'node:blog:page,user', + ] + ], + 'aliases' => ['dcea'], + 'examples' => array( + 'drush dcea' => 'Export all content entities on the site into the current directory.', + 'drush dcea --folder=/path/to/content' => 'Export all content entities into the specified directory.', + 'drush dcea --exclude=taxonomy_term' => 'Export all content entities except taxonomy terms.', + 'drush dcea --exclude=node:page:blog' => "Export all content entities except the node bundles 'page' and 'blog'.", + 'drush dcea --exclude=node:page,user' => "Export all content entities except the node bundle 'page' and users." + ), + ]; return $items; } @@ -107,3 +127,81 @@ function drush_default_content_export_module($module_name) { ->getPath() . '/content'; $exporter->writeDefaultContent($serialized_by_type, $module_folder); } + +/** + * Exports all content optionally filtered by entity type and/or bundle. + */ +function drush_default_content_export_all() { + // Filter out any non-content entity types. + /** @var \Drupal\Core\Entity\EntityTypeInterface[] $definitions */ + $definitions = \Drupal::entityTypeManager()->getDefinitions(); + $definitions = array_filter($definitions, function (EntityTypeInterface $definition) { + return is_a($definition->getClass(), ContentEntityInterface::class, TRUE); + }); + // Filter out entire entity types specified in the exclude option. + $exclude_option = drush_get_option('exclude'); + if ($exclude_option) { + $excluded = _default_content_drush_parse_exclude_option($exclude_option); + $definitions = array_filter($definitions, function (EntityTypeInterface $definition) use ($excluded) { + return !in_array($definition->id(), $excluded['entity_type']); + }); + } + $folder = drush_get_option('folder', '.'); + /** @var \Drupal\default_content\ExporterInterface $exporter */ + $exporter = \Drupal::service('default_content.exporter'); + /** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info */ + $bundle_info = \Drupal::service('entity_type.bundle.info'); + + drush_print(dt("Exporting"), 0, STDERR); + foreach ($definitions as $entity_type_id => $definition) { + // Filter out bundles specified in the exclude option. + $bundles = NULL; + if (!empty($excluded['bundle'][$entity_type_id])) { + $all_bundles = array_keys($bundle_info->getBundleInfo($entity_type_id)); + $bundles = array_diff($all_bundles, $excluded['bundle'][$entity_type_id]); + } + $serialized_by_type = $exporter->exportEntityTypeContent($entity_type_id, $bundles); + if ($serialized_by_type) { + $exporter->writeDefaultContent($serialized_by_type, $folder); + $count = count($serialized_by_type[$entity_type_id]); + drush_print($definition->getLabel() . ':' . $count, 1, STDERR); + } + } +} + +/** + * Parse the default-content-export-all exclude option. + * + * @param string $exclude_option + * The exclude option string. + * + * @return array + * An associative array keyed by: + * - entity_type: An array of entity type IDs to be fully excluded + * - bundle: An associative array of bundles to exclude, keyed by entity type + * ID. + * + * @see drush_default_content_export_all() + */ +function _default_content_drush_parse_exclude_option($exclude_option) { + $excluded_entity_types = []; + $excluded_bundles = []; + + foreach (explode(',', $exclude_option) as $value) { + $pieces = explode(':', $value); + // The first piece is the entity type ID. + $entity_type_id = array_shift($pieces); + // Any remaining pieces are bundles. + if ($pieces) { + $excluded_bundles[$entity_type_id] = $pieces; + } + else { + $excluded_entity_types[] = $entity_type_id; + } + } + + return [ + 'entity_type' => $excluded_entity_types, + 'bundle' => $excluded_bundles, + ]; +} diff --git a/src/Exporter.php b/src/Exporter.php index 0140568..738d961 100644 --- a/src/Exporter.php +++ b/src/Exporter.php @@ -153,6 +153,39 @@ class Exporter implements ExporterInterface { /** * {@inheritdoc} + * + * @todo Implement batching + */ + public function exportEntityTypeContent($entity_type_id, $bundles = NULL) { + $definition = $this->entityTypeManager->getDefinition($entity_type_id); + if (!is_a($definition->getClass(), ContentEntityInterface::class, TRUE)) { + throw new \InvalidArgumentException(sprintf('Entity type "%s" isn\'t a content entity', $entity_type_id)); + } + + $storage = $this->entityTypeManager->getStorage($entity_type_id); + $conditions = []; + if ($bundles) { + $bundle_key = $definition->getKey('bundle'); + if (!$bundle_key) { + throw new \InvalidArgumentException(sprintf('Entity "%s" doesn\'t have a bundle key, but bundles were specified.', $entity_type_id)); + } + $conditions[$bundle_key] = $bundles; + } + $entities = $storage->loadByProperties($conditions); + + $serialized_entities_per_type = []; + $this->linkManager->setLinkDomain($this->linkDomain); + // Serialize all entities and key them by entity TYPE and uuid. + foreach ($entities as $entity) { + $serialized_entities_per_type[$entity->getEntityTypeId()][$entity->uuid()] = $this->serializer->serialize($entity, 'hal_json', ['json_encode_options' => JSON_PRETTY_PRINT]); + } + $this->linkManager->setLinkDomain(FALSE); + + return $serialized_entities_per_type; + } + + /** + * {@inheritdoc} */ public function exportModuleContent($module_name) { $info_file = $this->moduleHandler->getModule($module_name)->getPathname(); diff --git a/src/ExporterInterface.php b/src/ExporterInterface.php index 20ea8bd..3ae5e95 100644 --- a/src/ExporterInterface.php +++ b/src/ExporterInterface.php @@ -48,6 +48,19 @@ interface ExporterInterface { public function exportModuleContent($module_name); /** + * Exports all of the content from specific bundles of an entity type. + * + * @param string $entity_type_id + * The entity type ID. + * @param string[]|null $bundles + * (Optional) An array of bundle names to export; defaults to all bundles. + * + * @return string[][] + * The serialized entities keyed by entity type and UUID. + */ + public function exportEntityTypeContent($entity_type_id, $bundles = NULL); + + /** * Writes an array of serialized entities to a given folder. * * @param string[][] $serialized_by_type