You are here

class MultiversionManager in Multiversion 8.2

Same name and namespace in other branches
  1. 8 src/MultiversionManager.php \Drupal\multiversion\MultiversionManager

Hierarchy

Expanded class hierarchy of MultiversionManager

1 string reference to 'MultiversionManager'
multiversion.services.yml in ./multiversion.services.yml
multiversion.services.yml
1 service uses MultiversionManager
multiversion.manager in ./multiversion.services.yml
Drupal\multiversion\MultiversionManager

File

src/MultiversionManager.php, line 21

Namespace

Drupal\multiversion
View source
class MultiversionManager implements MultiversionManagerInterface, ContainerAwareInterface {
  use ContainerAwareTrait;

  /**
   * @var \Drupal\workspaces\WorkspaceManagerInterface
   */
  protected $workspaceManager;

  /**
   * @var \Symfony\Component\Serializer\Serializer
   */
  protected $serializer;

  /**
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $entityFieldManager;

  /**
   * @var int
   */
  protected $lastSequenceId;

  /**
   * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
   * @param \Symfony\Component\Serializer\Serializer $serializer
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   * @param \Drupal\Core\State\StateInterface $state
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   * @param \Drupal\Core\Database\Connection $connection
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
   */
  public function __construct(WorkspaceManagerInterface $workspace_manager, Serializer $serializer, EntityTypeManagerInterface $entity_type_manager, StateInterface $state, LanguageManagerInterface $language_manager, CacheBackendInterface $cache, Connection $connection, EntityFieldManagerInterface $entity_field_manager) {
    $this->workspaceManager = $workspace_manager;
    $this->serializer = $serializer;
    $this->entityTypeManager = $entity_type_manager;
    $this->state = $state;
    $this->languageManager = $language_manager;
    $this->cache = $cache;
    $this->connection = $connection;
    $this->entityFieldManager = $entity_field_manager;
  }

  /**
   * Static method maintaining the enable status.
   *
   * This method needs to be static because in some strange situations Drupal
   * might create multiple instances of this manager. Is this only an issue
   * during tests perhaps?
   *
   * @param boolean|array $status
   * @return boolean|array
   */
  public static function enableIsActive($status = NULL) {
    static $cache = FALSE;
    if ($status !== NULL) {
      $cache = $status;
    }
    return $cache;
  }

  /**
   * Static method maintaining the disable status.
   *
   * @param boolean|array $status
   * @return boolean|array
   */
  public static function disableIsActive($status = NULL) {
    static $cache = FALSE;
    if ($status !== NULL) {
      $cache = $status;
    }
    return $cache;
  }

  /**
   * {@inheritdoc}
   *
   * @todo: {@link https://www.drupal.org/node/2597337 Consider using the
   * nextId API to generate more sequential IDs.}
   * @see \Drupal\Core\Database\Connection::nextId
   */
  public function newSequenceId() {

    // Multiply the microtime by 1 million to ensure we get an accurate integer.
    // Credit goes to @letharion and @logaritmisk for this simple but genius
    // solution.
    $this->lastSequenceId = (int) (microtime(TRUE) * 1000000);
    return $this->lastSequenceId;
  }

  /**
   * {@inheritdoc}
   */
  public function lastSequenceId() {
    return $this->lastSequenceId;
  }

  /**
   * {@inheritdoc}
   */
  public function isSupportedEntityType(EntityTypeInterface $entity_type) {
    $supported_entity_types = \Drupal::config('multiversion.settings')
      ->get('supported_entity_types') ?: [];
    if (empty($supported_entity_types)) {
      return FALSE;
    }
    if (!in_array($entity_type
      ->id(), $supported_entity_types)) {
      return FALSE;
    }
    return $entity_type instanceof ContentEntityTypeInterface;
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedEntityTypes() {
    $entity_types = [];
    foreach ($this->entityTypeManager
      ->getDefinitions() as $entity_type_id => $entity_type) {
      if ($this
        ->isSupportedEntityType($entity_type)) {
        $entity_types[$entity_type
          ->id()] = $entity_type;
      }
    }
    return $entity_types;
  }

  /**
   * {@inheritdoc}
   */
  public function isEnabledEntityType(EntityTypeInterface $entity_type) {
    if ($this
      ->isSupportedEntityType($entity_type)) {
      $entity_type_id = $entity_type
        ->id();
      $enabled_entity_types = \Drupal::config('multiversion.settings')
        ->get('enabled_entity_types') ?: [];
      if (in_array($entity_type_id, $enabled_entity_types)) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function allowToAlter(EntityTypeInterface $entity_type) {
    $supported_entity_types = \Drupal::config('multiversion.settings')
      ->get('supported_entity_types') ?: [];
    $id = $entity_type
      ->id();
    $enable_active = self::enableIsActive();
    $disable_migration = self::disableIsActive();

    // Don't allow to alter entity type that is not supported.
    if (!in_array($id, $supported_entity_types)) {
      return FALSE;
    }

    // Don't allow to alter entity type that is in process to be disabled.
    if (is_array($disable_migration) && in_array($id, $disable_migration)) {
      return FALSE;
    }

    // Allow to alter entity type that is in process to be enabled.
    if (is_array($enable_active) && in_array($id, $enable_active)) {
      return TRUE;
    }
    return $this
      ->isEnabledEntityType($entity_type);
  }

  /**
   * {@inheritdoc}
   */
  public function getEnabledEntityTypes() {
    $entity_types = [];
    foreach ($this
      ->getSupportedEntityTypes() as $entity_type_id => $entity_type) {
      if ($this
        ->isEnabledEntityType($entity_type)) {
        $entity_types[$entity_type_id] = $entity_type;
      }
    }
    return $entity_types;
  }

  /**
   * {@inheritdoc}
   */
  public function enableEntityTypes($entity_types_to_enable = NULL) {
    $entity_types = $entity_types_to_enable !== NULL ? $entity_types_to_enable : $this
      ->getSupportedEntityTypes();
    if (empty($entity_types)) {
      return $this;
    }

    // Temporarily disable the maintenance of the {comment_entity_statistics} table.
    $this->state
      ->set('comment.maintain_entity_statistics', FALSE);
    $multiversion_settings = \Drupal::configFactory()
      ->getEditable('multiversion.settings');
    $enabled_entity_types = $multiversion_settings
      ->get('enabled_entity_types') ?: [];
    $operations = [];
    $sandbox = [];

    // Define the step size.
    $sandbox['step_size'] = Settings::get('entity_conversion_batch_size', 50);
    foreach ($entity_types as $entity_type_id => $entity_type) {
      if (in_array($entity_type_id, $enabled_entity_types)) {
        continue;
      }
      $base_table = $entity_type
        ->getBaseTable();
      $sandbox['base_tables'][$entity_type_id] = $base_table;
      $entities_count = $this->connection
        ->select($base_table)
        ->countQuery()
        ->execute()
        ->fetchField();
      $i = 0;
      while ($i <= $entities_count) {
        $operations[] = [
          [
            get_class($this),
            'convertToMultiversionable',
          ],
          [
            $entity_type_id,
            $this->entityTypeManager,
            $this->state,
            $multiversion_settings,
            &$sandbox,
          ],
        ];
        $i += $sandbox['step_size'];
      }
      $operations[] = [
        [
          get_class($this),
          'fixPrimaryKeys',
        ],
        [
          $entity_type_id,
          $this->entityTypeManager,
          $this->connection,
        ],
      ];
    }

    // Create and process the batch.
    if (!empty($operations)) {
      $batch = [
        'operations' => $operations,
        'finished' => [
          get_class($this),
          'conversionFinished',
        ],
      ];
      batch_set($batch);
      $batch =& batch_get();
      $batch['progressive'] = FALSE;
      batch_process();
    }

    // Enable the the maintenance of entity statistics for comments.
    $this->state
      ->set('comment.maintain_entity_statistics', TRUE);
    self::enableIsActive(NULL);
    return $this;
  }
  public static function convertToMultiversionable($entity_type_id, EntityTypeManagerInterface $entity_type_manager, StateInterface $state, $multiversion_settings, &$sandbox) {
    self::enableIsActive([
      $entity_type_id,
    ]);
    $entity_type_manager
      ->useCaches(FALSE);
    $enabled_entity_types = $multiversion_settings
      ->get('enabled_entity_types') ?: [];

    /** @var \Drupal\multiversion\Entity\Storage\Sql\MultiversionStorageSchemaConverter $schema_converter */
    $schema_converter = \Drupal::service('multiversion.schema_converter_factory')
      ->getStorageSchemaConverter($entity_type_id);
    try {
      $schema_converter
        ->convertToMultiversionable($sandbox);
      if (isset($sandbox[$entity_type_id]['finished']) && $sandbox[$entity_type_id]['finished'] == 1 && !in_array($entity_type_id, $enabled_entity_types)) {
        $enabled_entity_types[] = $entity_type_id;
        $multiversion_settings
          ->set('enabled_entity_types', $enabled_entity_types)
          ->save();

        // Remove the entity from failed to convert entity types, if it's there.
        $failed_entity_types = $state
          ->get('multiversion.failed_entity_types', []);
        if (($key = array_search($entity_type_id, $failed_entity_types)) !== FALSE) {
          unset($failed_entity_types[$key]);
          $state
            ->set('multiversion.failed_entity_types', $failed_entity_types);
        }
      }
    } catch (\Exception $e) {
      $sandbox[$entity_type_id]['failed'] = TRUE;
      $failed_entity_types = $state
        ->get('multiversion.failed_entity_types', []);
      $arguments = Error::decodeException($e) + [
        '%entity_type' => $entity_type_id,
      ];
      \Drupal::logger('multiversion')
        ->warning('Entity type \'%entity_type\' failed to be converted to multiversionable. More info: %type: @message in %function (line %line of %file).', $arguments);
      $failed_entity_types[] = $entity_type_id;
      $state
        ->set('multiversion.failed_entity_types', $failed_entity_types);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function disableEntityTypes($entity_types_to_disable = NULL) {

    //    $entity_types = ($entity_types_to_disable !== NULL) ? $entity_types_to_disable : $this->getEnabledEntityTypes();
    //    if (empty($entity_types)) {
    //      return $this;
    //    }
    //
    //    // Temporarily disable the maintenance of the {comment_entity_statistics} table.
    //    $this->state->set('comment.maintain_entity_statistics', FALSE);
    //    $multiversion_settings = \Drupal::configFactory()
    //      ->getEditable('multiversion.settings');
    //    $enabled_entity_types = $multiversion_settings->get('enabled_entity_types') ?: [];
    //    $operations = [];
    //    $sandbox = [];
    //    // Define the step size.
    //    $sandbox['step_size'] = Settings::get('entity_conversion_batch_size', 50);
    //    foreach ($entity_types as $entity_type_id => $entity_type) {
    //      if (!in_array($entity_type_id, $enabled_entity_types)) {
    //        continue;
    //      }
    //      $base_table = $entity_type->getBaseTable();
    //      $sandbox['base_tables'][$entity_type_id] = $base_table;
    //      $entities_count = $this->connection->select($base_table)
    //        ->countQuery()
    //        ->execute()
    //        ->fetchField();
    //      $i = 0;
    //      while ($i <= $entities_count) {
    //        $operations[] = [
    //          [
    //            get_class($this),
    //            'convertToOriginalStorage',
    //          ],
    //          [
    //            $entity_type_id,
    //            $this->entityTypeManager,
    //            $this->state,
    //            $multiversion_settings,
    //            &$sandbox
    //          ],
    //        ];
    //        $i += $sandbox['step_size'];
    //      }
    //      $operations[] = [
    //        [
    //          get_class($this),
    //          'fixPrimaryKeys',
    //        ],
    //        [
    //          $entity_type_id,
    //          $this->entityTypeManager,
    //          $this->connection,
    //        ],
    //      ];
    //    }
    //
    //    // Create and process the batch.
    //    if (!empty($operations)) {
    //      $batch = [
    //        'operations' => $operations,
    //        'finished' => [get_class($this), 'conversionFinished']
    //      ];
    //      batch_set($batch);
    //      $batch =& batch_get();
    //      $batch['progressive'] = FALSE;
    //      batch_process();
    //    }
    //
    //    // Enable the the maintenance of entity statistics for comments.
    //    $this->state->set('comment.maintain_entity_statistics', TRUE);
    //    self::disableIsActive(NULL);
    return $this;
  }

  //  public static function convertToOriginalStorage($entity_type_id, EntityTypeManagerInterface $entity_type_manager, StateInterface $state, $multiversion_settings, &$sandbox) {
  //    self::disableIsActive([$entity_type_id]);
  //    $entity_type_manager->useCaches(FALSE);
  //    $enabled_entity_types = $multiversion_settings->get('enabled_entity_types') ?: [];
  //    /** @var \Drupal\multiversion\Entity\Storage\Sql\MultiversionStorageSchemaConverter $schema_converter */
  //    $schema_converter = \Drupal::service('multiversion.schema_converter_factory')
  //      ->getStorageSchemaConverter($entity_type_id);
  //
  //    try {
  //      $schema_converter->convertToOriginalStorage($sandbox);
  //      if (isset($sandbox[$entity_type_id]['finished'])
  //        && $sandbox[$entity_type_id]['finished'] == 1
  //        && in_array($entity_type_id, $enabled_entity_types)) {
  //        unset($enabled_entity_types[$entity_type_id]);
  //        $multiversion_settings
  //          ->set('enabled_entity_types', $enabled_entity_types)
  //          ->save();
  //        // Remove the entity from failed to convert entity types, if it's there.
  //        $failed_entity_types = $state->get('multiversion.failed_entity_types', []);
  //        if (($key = array_search($entity_type_id, $failed_entity_types)) !== FALSE) {
  //          unset($failed_entity_types[$key]);
  //          $state->set('multiversion.failed_entity_types', $failed_entity_types);
  //        }
  //      }
  //    }
  //    catch (\Exception $e) {
  //      $sandbox[$entity_type_id]['failed'] = TRUE;
  //      $failed_entity_types = $state->get('multiversion.failed_entity_types', []);
  //      $arguments = Error::decodeException($e) + ['%entity_type' => $entity_type_id];
  //      \Drupal::logger('multiversion')->warning('Entity type \'%entity_type\' failed to be converted to original storage. More info: %type: @message in %function (line %line of %file).', $arguments);
  //      $failed_entity_types[] = $entity_type_id;
  //      $state->set('multiversion.failed_entity_types', $failed_entity_types);
  //    }
  //  }

  /**
   * {@inheritdoc}
   */
  public function newRevisionId(ContentEntityInterface $entity, $index = 0) {
    $deleted = $entity->_deleted->value;
    $old_rev = $entity->_rev->value;

    // The 'new_revision_id' context will be used in normalizers (where it's
    // necessary) to identify in which format to return the normalized entity.
    $normalized_entity = $this->serializer
      ->normalize($entity, NULL, [
      'new_revision_id' => TRUE,
    ]);

    // Remove fields internal to the multiversion system.
    $this
      ->filterNormalizedEntity($normalized_entity);

    // The terms being serialized are:
    // - deleted
    // - old sequence ID (@todo: {@link https://www.drupal.org/node/2597341
    // Address this property.})
    // - old revision hash
    // - normalized entity (without revision info field)
    // - attachments (@todo: {@link https://www.drupal.org/node/2597341
    // Address this property.})
    return $index + 1 . '-' . md5($this
      ->termToBinary([
      $deleted,
      0,
      $old_rev,
      $normalized_entity,
      [],
    ]));
  }

  /**
   * @param array $normalized_entity
   */
  protected function filterNormalizedEntity(&$normalized_entity) {
    foreach ($normalized_entity as $key => &$value) {
      if ($key[0] == '_') {
        unset($normalized_entity[$key]);
      }
      elseif (is_array($value)) {
        $this
          ->filterNormalizedEntity($value);
      }
    }
  }
  protected function termToBinary(array $term) {

    // @todo: {@link https://www.drupal.org/node/2597478 Switch to BERT
    // serialization format instead of JSON.}
    return $this->serializer
      ->serialize($term, 'json');
  }
  static function fixPrimaryKeys($entity_type_id, EntityTypeManagerInterface $entity_type_manager, Connection $connection) {
    $storage = $entity_type_manager
      ->getStorage($entity_type_id);
    $entity_type = $storage
      ->getEntityType();

    // Make sure that 'id', 'revision' and 'langcode' are primary keys.
    $schema = $connection
      ->schema();

    // Fix primary key in the base table.
    $base_table = $storage
      ->getBaseTable();
    try {
      $id_key = $entity_type
        ->getKey('id');
      $schema
        ->dropPrimaryKey($base_table);
      $connection
        ->query('ALTER TABLE {' . $base_table . '} MODIFY COLUMN ' . $id_key . ' INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY');
    } catch (\Exception $e) {

      // Do nothing, the index already exists.
    }

    // Fix primary key in the revision table.
    if ($revision_table = $storage
      ->getRevisionTable()) {
      try {
        $revision_key = $entity_type
          ->getKey('revision');
        $schema
          ->dropPrimaryKey($revision_table);
        $connection
          ->query('ALTER TABLE {' . $revision_table . '} MODIFY COLUMN ' . $revision_key . ' INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY');
      } catch (\Exception $e) {

        // Do nothing, the index already exists.
      }
    }

    // Fix primary key in the data table.
    if ($entity_type
      ->isTranslatable() && ($data_table = $storage
      ->getDataTable())) {
      try {
        $schema
          ->dropPrimaryKey($data_table);
        $schema
          ->addPrimaryKey($data_table, [
          $entity_type
            ->getKey('id'),
          $entity_type
            ->getKey('langcode'),
        ]);
      } catch (\Exception $e) {

        // Do nothing, the index already exists.
      }
    }

    // Fix primary key in the revision data table.
    if ($entity_type
      ->isTranslatable() && ($revision_data_table = $storage
      ->getRevisionDataTable())) {
      try {
        $schema
          ->dropPrimaryKey($revision_data_table);
        $schema
          ->addPrimaryKey($revision_data_table, [
          $entity_type
            ->getKey('revision'),
          $entity_type
            ->getKey('langcode'),
        ]);
      } catch (\Exception $e) {

        // Do nothing, the index already exists.
      }
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
MultiversionManager::$cache protected property
MultiversionManager::$connection protected property The database connection.
MultiversionManager::$entityFieldManager protected property The entity field manager.
MultiversionManager::$entityTypeManager protected property
MultiversionManager::$languageManager protected property
MultiversionManager::$lastSequenceId protected property
MultiversionManager::$serializer protected property
MultiversionManager::$state protected property
MultiversionManager::$workspaceManager protected property
MultiversionManager::allowToAlter public function Overrides MultiversionManagerInterface::allowToAlter
MultiversionManager::convertToMultiversionable public static function
MultiversionManager::disableEntityTypes public function Overrides MultiversionManagerInterface::disableEntityTypes
MultiversionManager::disableIsActive public static function Static method maintaining the disable status.
MultiversionManager::enableEntityTypes public function Overrides MultiversionManagerInterface::enableEntityTypes
MultiversionManager::enableIsActive public static function Static method maintaining the enable status.
MultiversionManager::filterNormalizedEntity protected function
MultiversionManager::fixPrimaryKeys static function
MultiversionManager::getEnabledEntityTypes public function Overrides MultiversionManagerInterface::getEnabledEntityTypes
MultiversionManager::getSupportedEntityTypes public function Overrides MultiversionManagerInterface::getSupportedEntityTypes
MultiversionManager::isEnabledEntityType public function Overrides MultiversionManagerInterface::isEnabledEntityType
MultiversionManager::isSupportedEntityType public function Overrides MultiversionManagerInterface::isSupportedEntityType
MultiversionManager::lastSequenceId public function Overrides MultiversionManagerInterface::lastSequenceId
MultiversionManager::newRevisionId public function Overrides MultiversionManagerInterface::newRevisionId
MultiversionManager::newSequenceId public function @todo: {@link https://www.drupal.org/node/2597337 Consider using the nextId API to generate more sequential IDs.} Overrides MultiversionManagerInterface::newSequenceId
MultiversionManager::termToBinary protected function
MultiversionManager::__construct public function