content_sync-8.x-2.x-dev/src/Drush/Commands/ContentSyncDrushCommands.php

src/Drush/Commands/ContentSyncDrushCommands.php
<?php

namespace Drupal\content_sync\Drush\Commands;

use Drupal\content_sync\Content\ContentStorageComparer;
use Drupal\content_sync\ContentSyncManagerInterface;
use Drupal\content_sync\Exporter\ContentExporterInterface;
use Drupal\content_sync\Form\ContentExportTrait;
use Drupal\content_sync\Form\ContentImportTrait;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Drush\Exceptions\UserAbortException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Provides drush commands for importing and exporting content.
 */
class ContentSyncDrushCommands extends DrushCommands {

  use ContentExportTrait;
  use ContentImportTrait;
  use DependencySerializationTrait;
  use StringTranslationTrait;

  /**
   * The configuration manager.
   *
   * @var \Drupal\Core\Config\ConfigManagerInterface
   */
  protected $configManager;

  /**
   * The content storage.
   *
   * @var \Drupal\Core\Config\StorageInterface
   */
  protected $contentStorage;

  /**
   * The content sync storage.
   *
   * @var \Drupal\Core\Config\StorageInterface
   */
  protected $contentStorageSync;

  /**
   * The content sync manager.
   *
   * @var \Drupal\content_sync\ContentSyncManagerInterface
   */
  protected $contentSyncManager;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The content exporter.
   *
   * @var \Drupal\content_sync\Exporter\ContentExporterInterface
   */
  protected $contentExporter;

  /**
   * The lock information for this configuration.
   *
   * @var \Drupal\Core\TempStore\Lock|null
   */
  protected $lock;

  /**
   * The typed config manager.
   *
   * @var \Drupal\Core\Config\TypedConfigManagerInterface
   */
  protected $typedConfigManager;

  /**
   * The module installer.
   *
   * @var \Drupal\Core\Extension\ModuleInstallerInterface
   */
  protected $moduleInstaller;

  /**
   * The theme handler.
   *
   * @var \Drupal\Core\Extension\ThemeHandlerInterface
   */
  protected $themeHandler;

  /**
   * The string translation service.
   *
   * @var \Drupal\Core\StringTranslation\TranslationInterface
   */
  protected $stringTranslation;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * Gets the configManager.
   *
   * @return \Drupal\Core\Config\ConfigManagerInterface
   *   The configManager.
   */
  public function getConfigManager() {
    return $this->configManager;
  }

  /**
   * Gets the contentStorage.
   *
   * @return \Drupal\Core\Config\StorageInterface
   *   The contentStorage.
   */
  public function getContentStorage() {
    return $this->contentStorage;
  }

  /**
   * Gets the contentStorageSync.
   *
   * @return \Drupal\Core\Config\StorageInterface
   *   The contentStorageSync.
   */
  public function getContentStorageSync() {
    return $this->contentStorageSync;
  }

  /**
   * {@inheritdoc}
   */
  protected function getEntityTypeManager() {
    return $this->entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  protected function getContentExporter() {
    return $this->contentExporter;
  }

  /**
   * {@inheritdoc}
   */
  protected function getExportLogger() {
    return $this->logger('content_sync');
  }

  /**
   * ContentSyncCommands constructor.
   *
   * @param \Drupal\Core\Config\ConfigManagerInterface $configManager
   *   The configManager.
   * @param \Drupal\Core\Config\StorageInterface $contentStorage
   *   The contentStorage.
   * @param \Drupal\Core\Config\StorageInterface $contentStorageSync
   *   The contentStorageSync.
   * @param \Drupal\content_sync\ContentSyncManagerInterface $contentSyncManager
   *   The contentSyncManager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entityTypeManager.
   * @param \Drupal\content_sync\Exporter\ContentExporterInterface $content_exporter
   *   The contentExporter.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The moduleHandler.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
   *   The eventDispatcher.
   * @param \Drupal\Core\Lock\LockBackendInterface $lock
   *   The lock.
   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
   *   The typedConfigManager.
   * @param \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller
   *   The moduleInstaller.
   * @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
   *   The themeHandler.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation
   *   The stringTranslation.
   */
  public function __construct(ConfigManagerInterface $configManager, StorageInterface $contentStorage, StorageInterface $contentStorageSync, ContentSyncManagerInterface $contentSyncManager, EntityTypeManagerInterface $entity_type_manager, ContentExporterInterface $content_exporter, ModuleHandlerInterface $moduleHandler, EventDispatcherInterface $eventDispatcher, LockBackendInterface $lock, TypedConfigManagerInterface $typedConfigManager, ModuleInstallerInterface $moduleInstaller, ThemeHandlerInterface $themeHandler, TranslationInterface $stringTranslation) {
    parent::__construct();
    $this->configManager = $configManager;
    $this->contentStorage = $contentStorage;
    $this->contentStorageSync = $contentStorageSync;
    $this->contentSyncManager = $contentSyncManager;
    $this->entityTypeManager = $entity_type_manager;
    $this->contentExporter = $content_exporter;
    $this->moduleHandler = $moduleHandler;
    $this->eventDispatcher = $eventDispatcher;
    $this->lock = $lock;
    $this->typedConfigManager = $typedConfigManager;
    $this->moduleInstaller = $moduleInstaller;
    $this->themeHandler = $themeHandler;
    $this->stringTranslation = $stringTranslation;
  }

  /**
   * Create a new instance of the class.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The container.
   *
   * @return static
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.manager'),
      $container->get('content.storage'),
      $container->get('content.storage.sync'),
      $container->get('content_sync.manager'),
      $container->get('entity_type.manager'),
      $container->get('content_sync.exporter'),
      $container->get('module_handler'),
      $container->get('event_dispatcher'),
      $container->get('lock'),
      $container->get('config.typed'),
      $container->get('module_installer'),
      $container->get('theme_handler'),
      $container->get('string_translation')
    );
  }

  /**
   * Import content from a content directory.
   */
  #[CLI\Command(name: 'content-sync:import', aliases: [
    'csi',
    'content-sync-import',
  ])]
  #[CLI\Argument(name: 'label', description: 'A content directory label (i.e. a key in $content_directories array in settings.php).')]
  #[CLI\Option(name: 'entity-types', description: 'A list of entity type names separated by commas.')]
  #[CLI\Option(name: 'uuids', description: 'A list of UUIDs separated by commas.')]
  #[CLI\Option(name: 'actions', description: 'A list of Actions separated by commas.')]
  #[CLI\Option(name: 'skiplist', description: 'Skip the change list before proceed with the import.')]
  #[CLI\Usage(name: 'drush content-sync-import', description: 'Usage description')]
  public function import($label = NULL, array $options = [
    'entity-types' => '',
    'uuids' => '',
    'actions' => '',
    'skiplist' => FALSE,
    'compare-dates' => FALSE,
  ]) {

    // Generate comparer with filters.
    $storage_comparer = new ContentStorageComparer($this->contentStorageSync, $this->contentStorage);
    $change_list = [];
    $collections = $storage_comparer->getAllCollectionNames();
    if (!empty($options['entity-types'])) {
      $entity_types = explode(',', $options['entity-types']);
      $match_collections = [];
      foreach ($entity_types as $entity_type) {
        $match_collections = $match_collections + preg_grep('/^' . $entity_type . '/', $collections);
      }
      $collections = $match_collections;
    }

    foreach ($collections as $collection) {
      if (!empty($options['uuids'])) {
        $storage_comparer->createChangelistbyCollectionAndNames($collection, $options['uuids']);
      }
      elseif ($options['compare-dates']) {
        $storage_comparer->createChangelistbyCollection($collection, TRUE);
      }
      else {
        $storage_comparer->createChangelistbyCollection($collection);
      }
      if (!empty($options['actions'])) {
        $actions = explode(',', $options['actions']);
        foreach ($actions as $op) {
          if (in_array($op, ['create', 'update', 'delete'])) {
            $change_list[$collection][$op] = $storage_comparer->getChangelist($op, $collection);
          }
        }
      }
      else {
        $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
      }
      $change_list = array_map('array_filter', $change_list);
      $change_list = array_filter($change_list);
    }
    unset($change_list['']);

    if (empty($change_list)) {
      $this->logger()->notice(dt('Nothing to import, the active content is identical to the content in files.'));
      return;
    }

    // Display the change list.
    if (empty($options['skiplist'])) {
      // Show differences.
      $this->output()
        ->writeln("Differences of the export directory to the active content:\n");
      // Print a table with changes in color.
      $table = self::contentChangesTable($change_list, $this->output());
      $table->render();
      // Ask to continue.
      if (!$this->io()
        ->confirm(dt('Do you want to import?'))) {
        throw new UserAbortException();
      }
    }

    // Process import data.
    $content_to_sync = [];
    $content_to_delete = [];
    foreach ($change_list as $collection => $actions) {
      if (!empty($actions['create'])) {
        $content_to_sync = array_merge($content_to_sync, $actions['create']);
      }
      if (!empty($actions['update'])) {
        $content_to_sync = array_merge($content_to_sync, $actions['update']);
      }
      if (!empty($actions['delete'])) {
        $content_to_delete = $actions['delete'];
      }
    }
    // Set the Import Batch.
    if (!empty($content_to_sync) || !empty($content_to_delete)) {
      $batch = $this->generateImportBatch($content_to_sync,
        $content_to_delete);
      batch_set($batch);
      drush_backend_batch_process();
    }
  }

  /**
   * Export Drupal content to a directory.
   */
  #[CLI\Command(name: 'content-sync:export', aliases: [
    'cse',
    'content-sync-export',
  ])]
  #[CLI\Argument(name: 'label', description: 'A content directory label (i.e. a key in $content_directories array in settings.php).')]
  #[CLI\Option(name: 'entity-types', description: 'A list of entity type names separated by commas.')]
  #[CLI\Option(name: 'uuids', description: 'A list of UUIDs separated by commas.')]
  #[CLI\Option(name: 'actions', description: 'A list of Actions separated by commas.')]
  #[CLI\Option(name: 'files', description: 'A value none/base64/folder - default folder.')]
  #[CLI\Option(name: 'include-dependencies', description: 'Export content dependencies.')]
  #[CLI\Option(name: 'skiplist', description: 'Skip the change list before proceed with the export.')]
  #[CLI\Usage(name: 'drush content-sync-export', description: 'Usage description')]
  public function export($label = NULL, array $options = [
    'entity-types' => '',
    'uuids' => '',
    'actions' => '',
    'files' => '',
    'include-dependencies' => FALSE,
    'skiplist' => FALSE,
    'compare-dates' => FALSE,
    'force' => FALSE,
  ]) {

    // Don't rely on the snapshot/diffs
    if (!empty($options['force'])){
      $entities_allowed = [];
      if (!empty($options['entity-types'])){
        $entity_allowed = explode(',', $options['entity-types']);
        foreach ($entity_allowed as $key => $entity){
          list($type,$bundle) = explode('.', $entity);
          $entities_allowed[$type][] = $bundle;
        }
      }
      //Set batch operations by entity type/bundle
      $entities_list = [];
      $entity_type_definitions = $this->entityTypeManager->getDefinitions();
      $entities_types_allowed = array_keys($entities_allowed);
      foreach ($entity_type_definitions as $entity_type => $definition) {
        $reflection = new \ReflectionClass($definition->getClass());
        if ($reflection->implementsInterface(ContentEntityInterface::class)) {
          if (empty($options['entity-types']) || in_array($entity_type, $entities_types_allowed)) {
            $storage = $this->entityTypeManager->getStorage($entity_type);
            $query = $storage->getQuery();
            $query->accessCheck(FALSE);
            if(!empty($options['entity-types'])){
              $bundles_allowed = array_filter($entities_allowed[$entity_type]);
              $query->condition('type', $bundles_allowed, 'in');
            }
            if (!empty($options['uuids'])){
              $uuids = explode(',', $options['uuids']);
              $query->condition('uuid', $uuids, 'in');
            }
            $entities = $query->execute();
            foreach ($entities as $entity_id) {
              $entities_list[] = [
                'entity_type' => $entity_type,
                'entity_id' => $entity_id,
              ];
            }
          }
        }
      }
    }
    else {
      // Generate comparer with filters.
      $storage_comparer = new ContentStorageComparer($this->contentStorage, $this->contentStorageSync);
      $change_list = [];
      $collections = $storage_comparer->getAllCollectionNames();
      if (!empty($options['entity-types'])) {
        $entity_types = explode(',', $options['entity-types']);
        $match_collections = [];
        foreach ($entity_types as $entity_type) {
          $match_collections = $match_collections + preg_grep('/^' . $entity_type . '/', $collections);
        }
        $collections = $match_collections;
      }
      foreach ($collections as $collection) {
        if (!empty($options['uuids'])) {
          $storage_comparer->createChangelistbyCollectionAndNames($collection, $options['uuids']);
        }
        else {
          $storage_comparer->createChangelistbyCollection($collection);
        }
        if (!empty($options['actions'])) {
          $actions = explode(',', $options['actions']);
          foreach ($actions as $op) {
            if (in_array($op, ['create', 'update', 'delete'])) {
              $change_list[$collection][$op] = $storage_comparer->getChangelist($op, $collection);
            }
          }
        }
        elseif ($options['compare-dates']) {
          $storage_comparer->createChangelistbyCollection($collection, TRUE);
        }
        else {
          $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
        }
        $change_list = array_map('array_filter', $change_list);
        $change_list = array_filter($change_list);
      }
      unset($change_list['']);

      if (empty($change_list)) {
        $this->logger()->notice(dt('Nothing to export, the active content is identical to the content in files.'));
        return;
      }

      // Display the change list.
      if (empty($options['skiplist'])) {
        // Show differences.
        $this->output()
          ->writeln("Differences of the active content to the export directory:\n");
        // Print a table with changes in color.
        $table = self::contentChangesTable($change_list, $this->output());
        $table->render();
        // Ask to continue.
        if (!$this->io()
          ->confirm(dt('Do you want to export?'))) {
          throw new UserAbortException();
        }
      }

      // Process the Export.
      $entities_list = [];
      foreach ($change_list as $collection => $changes) {
        // $storage_comparer->getTargetStorage($collection)->deleteAll();
        foreach ($changes as $change => $contents) {
          switch ($change) {
            case 'delete':
              foreach ($contents as $content) {
                $storage_comparer->getTargetStorage($collection)
                  ->delete($content);
              }
              break;
            case 'update':
            case 'create':
              foreach ($contents as $content) {
                $entity = explode('.', $content);
                $entities_list[] = [
                  'entity_type' => $entity[0],
                  'entity_uuid' => $entity[2],
                ];
              }
              break;
          }
        }
      }
    }

    // Files options.
    $include_files = self::processFilesOption($options);

    // Set the Export Batch.
    if (!empty($entities_list)) {
      $batch = $this->generateExportBatch($entities_list,
        [
          'export_type' => 'folder',
          'include_files' => $include_files,
          'include_dependencies' => $options['include-dependencies'],
        ]);
      batch_set($batch);
      drush_backend_batch_process();
    }
  }

  /**
   * Builds a table of content changes.
   *
   * @param array $content_changes
   *   An array of changes keyed by collection.
   * @param \Symfony\Component\Console\Output\OutputInterface $output
   *   The output.
   * @param bool $use_color
   *   If it should use color.
   *
   * @return \Symfony\Component\Console\Helper\Table
   *   A Symfony table object.
   */
  public static function contentChangesTable(array $content_changes, OutputInterface $output, $use_color = TRUE) {
    $rows = [];
    foreach ($content_changes as $collection => $changes) {
      if (is_array($changes)) {
        foreach ($changes as $change => $contents) {
          switch ($change) {
            case 'delete':
              $colour = '<fg=white;bg=red>';
              break;

            case 'update':
              $colour = '<fg=black;bg=yellow>';
              break;

            case 'create':
              $colour = '<fg=white;bg=green>';
              break;

            default:
              $colour = "<fg=black;bg=cyan>";
              break;
          }
          if ($use_color) {
            $prefix = $colour;
            $suffix = '</>';
          }
          else {
            $prefix = $suffix = '';
          }
          foreach ($contents as $content) {
            $rows[] = [
              $collection,
              $content,
              $prefix . ucfirst($change) . $suffix,
            ];
          }
        }
      }
    }
    $table = new Table($output);
    $table->setHeaders(['Collection', 'Content Name', 'Operation']);
    $table->addRows($rows);
    return $table;
  }

  /**
   * Processes 'files' option.
   *
   * @param array $options
   *   The command options.
   *
   * @return string
   *   Processed 'files' option value.
   */
  public static function processFilesOption(array $options) {
    $include_files = !empty($options['files']) ? $options['files'] : 'folder';
    if (!in_array($include_files, ['folder', 'base64'])) {
      $include_files = 'none';
    }
    return $include_files;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc