You are here

multiversion.module in Multiversion 8.2

Same filename and directory in other branches
  1. 8 multiversion.module

File

multiversion.module
View source
<?php

use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\workspaces\Entity\Workspace;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ViewExecutable;

/**
 * Implements hook_module_implements_alter().
 */
function multiversion_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'entity_type_alter') {
    $group = $implementations['multiversion'];
    unset($implementations['multiversion']);
    $implementations = [
      'multiversion' => $group,
    ] + $implementations;
  }
  if ($hook == 'field_info_alter') {
    $group = $implementations['multiversion'];
    unset($implementations['multiversion']);
    $implementations['multiversion'] = $group;
  }
}

/**
 * Implements hook_entity_type_alter().
 *
 * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
 */
function multiversion_entity_type_alter(array &$entity_types) {

  /** @var \Drupal\multiversion\MultiversionManagerInterface $manager */
  $manager = \Drupal::service('multiversion.manager');
  foreach ($entity_types as $entity_type) {
    if ($manager
      ->allowToAlter($entity_type)) {
      $entity_keys = $entity_type
        ->getKeys();

      // Add the 'published' entity key to the entity type.
      if (empty($entity_keys['published'])) {
        $entity_keys['published'] = 'status';
        $entity_type
          ->set('entity_keys', $entity_keys);
      }

      // Make all content entity types revisionable.
      if (!$entity_type
        ->isRevisionable()) {

        // We only need to set the revision key to make an entity type
        // revisionable. The table names will be handled by the storage class.
        // @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout
        $entity_keys['revision'] = 'revision_id';
        $entity_type
          ->set('entity_keys', $entity_keys);
        if ($entity_type
          ->getRevisionTable() === null) {
          $entity_type
            ->set('revision_table', $entity_type
            ->id() . '_revision');
        }
        if ($entity_type
          ->getRevisionDataTable() === null) {
          $entity_type
            ->set('revision_data_table', $entity_type
            ->id() . '_field_revision');
        }
      }
      $namespace = 'Drupal\\multiversion\\Entity\\Storage\\Sql';
      $original_storage_class = $entity_type
        ->getHandlerClass('storage');
      $entity_type
        ->setHandlerClass('original_storage', $original_storage_class);
      switch ($entity_type
        ->id()) {
        case 'node':
          $entity_type
            ->setHandlerClass('storage', "{$namespace}\\NodeStorage");
          break;
        case 'taxonomy_term':
          $entity_type
            ->setHandlerClass('storage', "{$namespace}\\TermStorage");
          break;
        case 'comment':
          $entity_type
            ->setHandlerClass('storage', "{$namespace}\\CommentStorage");
          break;
        case 'menu_link_content':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\MenuLinkContent');
          $entity_type
            ->setHandlerClass('storage', "{$namespace}\\MenuLinkContentStorage");
          break;
        case 'file':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\File');
          $entity_type
            ->setHandlerClass('storage', "{$namespace}\\FileStorage");
          break;
        case 'media':
          $entity_type
            ->setHandlerClass('storage', "{$namespace}\\MediaStorage");
          break;
        case 'shortcut':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\Shortcut');
          if (in_array($original_storage_class, [
            NULL,
            'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage',
          ])) {
            $entity_type
              ->setHandlerClass('storage', "{$namespace}\\ContentEntityStorage");
          }
          break;
        case 'entity_test':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\EntityTest');
          if (in_array($original_storage_class, [
            NULL,
            'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage',
          ])) {
            $entity_type
              ->setHandlerClass('storage', "{$namespace}\\ContentEntityStorage");
          }
          break;
        case 'entity_test_mul':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\EntityTestMul');
          if (in_array($original_storage_class, [
            NULL,
            'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage',
          ])) {
            $entity_type
              ->setHandlerClass('storage', "{$namespace}\\ContentEntityStorage");
          }
          break;
        case 'entity_test_rev':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\EntityTestRev');
          if (in_array($original_storage_class, [
            NULL,
            'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage',
          ])) {
            $entity_type
              ->setHandlerClass('storage', "{$namespace}\\ContentEntityStorage");
          }
          break;
        case 'entity_test_mulrev':
          $entity_type
            ->setClass('Drupal\\multiversion\\Entity\\EntityTestMulRev');
          if (in_array($original_storage_class, [
            NULL,
            'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage',
          ])) {
            $entity_type
              ->setHandlerClass('storage', "{$namespace}\\ContentEntityStorage");
          }
          break;
        default:
          $storage_class = $entity_type
            ->getHandlerClass('storage');

          // We can only override the storage handler for entity types we know
          // what to expect of.
          if (in_array($storage_class, [
            NULL,
            'Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage',
          ])) {
            $entity_type
              ->setHandlerClass('storage', "{$namespace}\\ContentEntityStorage");
          }
          break;
      }
    }
  }
  if (isset($entity_types['block_content']) && $manager
    ->allowToAlter($entity_types['block_content'])) {
    $entity_types['block']
      ->setHandlerClass('storage', 'Drupal\\multiversion\\Entity\\Storage\\Sql\\BlockStorage');
  }
}

/**
 * Implements hook_entity_base_field_info().
 *
 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
 * @return \Drupal\Core\Field\BaseFieldDefinition[]
 */
function multiversion_entity_base_field_info(EntityTypeInterface $entity_type) {

  /** @var \Drupal\multiversion\MultiversionManagerInterface $manager */
  $manager = \Drupal::service('multiversion.manager');
  if ($manager
    ->allowToAlter($entity_type)) {
    $fields = [];

    // Get the 'published' key for the published status field.
    $published_key = $entity_type
      ->getKey('published') ?: 'status';

    // Add the status field.
    if (empty($fields[$published_key])) {
      $fields[$published_key] = BaseFieldDefinition::create('boolean')
        ->setName($published_key)
        ->setTargetEntityTypeId($entity_type
        ->id())
        ->setTargetBundle(NULL)
        ->setLabel(t('Publishing status'))
        ->setDescription(t('A boolean indicating the published state.'))
        ->setRevisionable(TRUE)
        ->setTranslatable(TRUE)
        ->setDefaultValue(TRUE);
    }
    $fields['_deleted'] = BaseFieldDefinition::create('boolean')
      ->setName('_deleted')
      ->setTargetEntityTypeId($entity_type
      ->id())
      ->setTargetBundle(NULL)
      ->setLabel(t('Deleted flag'))
      ->setDescription(t('Indicates if the entity is flagged as deleted or not.'))
      ->setRevisionable(TRUE)
      ->setTranslatable(FALSE)
      ->setDefaultValue(FALSE)
      ->setCardinality(1);
    $fields['_rev'] = BaseFieldDefinition::create('revision_token')
      ->setName('_rev')
      ->setTargetEntityTypeId($entity_type
      ->id())
      ->setTargetBundle(NULL)
      ->setLabel(t('Revision token'))
      ->setDescription(t('The token for this entity revision.'))
      ->setRevisionable(TRUE)
      ->setTranslatable(FALSE)
      ->setCardinality(1)
      ->setReadOnly(TRUE);
    return $fields;
  }
}

/**
 * Implements hook_data_type_info_alter().
 */
function multiversion_data_type_info_alter(&$info) {
  $info['entity_reference']['class'] = '\\Drupal\\multiversion\\EntityReference';
}

/**
 * Implements hook_field_info_alter().
 */
function multiversion_field_info_alter(&$info) {
  $info['entity_reference']['class'] = '\\Drupal\\multiversion\\EntityReferenceItem';
  $info['file']['class'] = '\\Drupal\\multiversion\\FileItem';
  $info['image']['class'] = '\\Drupal\\multiversion\\ImageItem';
  if (isset($info['entity_reference_revisions'])) {
    $info['entity_reference_revisions']['class'] = '\\Drupal\\multiversion\\EntityReferenceRevisionsItem';
  }
}

/**
 * Implements hook_entity_base_field_info_alter().
 *
 * @param array $fields
 * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
 */
function multiversion_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {

  /** @var \Drupal\multiversion\MultiversionManagerInterface $manager */
  $manager = \Drupal::service('multiversion.manager');
  if ($manager
    ->allowToAlter($entity_type)) {
    $exclude_fields = [
      $entity_type
        ->getKey('id'),
      $entity_type
        ->getKey('revision') ?: 'revision_id',
      $entity_type
        ->getKey('uuid'),
      $entity_type
        ->getKey('bundle'),
      '_deleted',
      '_rev',
    ];
    foreach ($fields as $key => $field) {
      if (!in_array($key, $exclude_fields)) {
        $field
          ->setRevisionable(TRUE);
      }
    }
  }
}

/**
 * Implements hook_views_post_execute().
 */
function multiversion_views_post_execute(ViewExecutable $view) {

  // Add deleted entities if we have rows for them.
  // When we want to get deleted entities using the _deleted field, entities
  // should be loaded with
  // \Drupal::entityManager()->getTypeStorage($entity_type)->loadDeleted($id) or
  // \Drupal::entityManager()->getTypeStorage($entity_type)->loadMultipleDeleted($ids),
  // otherwise the _entity field in the view result rows will be null.
  $base_field = $view->storage
    ->get('base_field');
  $table_info = $view->query
    ->getEntityTableInfo();
  $content_type_info = array_column($table_info, 'entity_type');
  if (is_array($view->result) && ($content_type = reset($content_type_info))) {
    $manager = \Drupal::service('multiversion.manager');
    $storage = \Drupal::entityTypeManager()
      ->getStorage($content_type);
    if ($manager
      ->allowToAlter($storage
      ->getEntityType())) {
      $ids = [];
      foreach ($view->result as $index => $row) {
        if (empty($row->_entity) && !empty($row->{$base_field})) {
          $ids[$index] = $row->{$base_field};
        }
      }
      $entities = $storage
        ->loadMultipleDeleted($ids);
      foreach ($view->result as $index => $row) {
        if (empty($row->_entity) && !empty($row->{$base_field}) && isset($entities[$row->{$base_field}])) {
          $view->result[$index]->_entity = $entities[$row->{$base_field}];
        }
        elseif (empty($row->_entity)) {
          unset($view->result[$index]);
        }
      }
    }
  }
}

/**
 * Implements hook_views_query_alter().
 *
 * @param \Drupal\views\ViewExecutable $view
 *   The view object about to be processed.
 * @param QueryPluginBase $query
 *   The query plugin object for the query.
 */
function multiversion_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {

  // Add a new filter for default core views, it will filter deleted content.
  $views_ids = [
    'content',
    'frontpage',
    'comments_recent',
    'content_recent',
    'taxonomy_term',
    'glossary',
    'archive',
    'block_content',
  ];
  if (in_array($view
    ->id(), $views_ids)) {

    /** @var \Drupal\multiversion\MultiversionManagerInterface $manager */
    $manager = \Drupal::service('multiversion.manager');
    $entity_type = $view
      ->getBaseEntityType();
    if (!$manager
      ->isEnabledEntityType($entity_type)) {
      return;
    }
    $base_table = $view->storage
      ->get('base_table');
    $view->query->where[1]['conditions'][] = [
      'field' => $base_table . '._deleted',
      'value' => FALSE,
      'operator' => '=',
    ];
  }
}

/**
 * Implements hook_query_TAG_alter().
 */
function multiversion_query_entity_query_alter(AlterableInterface $query) {
  $entity_type_id = $query
    ->getMetaData('entity_type');
  $entity_type = \Drupal::entityTypeManager()
    ->getDefinition($entity_type_id);
  if ($entity_type_id && \Drupal::service('multiversion.manager')
    ->isEnabledEntityType($entity_type)) {
    $revision_key = $entity_type
      ->getKey('revision');
    $data_table = $entity_type
      ->getDataTable();
    if ($data_table) {
      if (!in_array($data_table, array_column($query
        ->getTables(), 'table'))) {
        $query
          ->join($data_table, NULL, 'base_table.' . $revision_key . '=' . $data_table . '.' . $revision_key);
      }
    }
  }
}

/**
 * Implements hook_query_TAG_alter().
 *
 * @see \Drupal\taxonomy\TermStorage::loadTree().
 */
function multiversion_query_taxonomy_term_access_alter(AlterableInterface $query) {
  $active_workspace = \Drupal::service('workspaces.manager')
    ->getActiveWorkspace();
  if (!$active_workspace
    ->isDefaultWorkspace() && !$query
    ->hasTag('entity_query_taxonomy_term')) {
    $workspace_association_table = 'workspace_association';
    $query
      ->leftJoin($workspace_association_table, $workspace_association_table, "%alias.target_entity_type_id = 'taxonomy_term' AND %alias.target_entity_id = t.tid");
    $query
      ->condition($query
      ->orConditionGroup()
      ->condition("{$workspace_association_table}.workspace", $active_workspace
      ->id())
      ->condition("{$workspace_association_table}.workspace", NULL, 'IS'));
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function multiversion_form_node_type_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {

  // Users don't have the option to disable revisions when using Multiversion.
  // @todo: {@link https://www.drupal.org/node/2597393 See if there's a way
  // to just disable this particular option.}
  unset($form['workflow']['options']['#options']['revision']);
}

/**
 * Implements hook_form_alter().
 */
function multiversion_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if (isset($form['revision']['#group']) && $form['revision']['#group'] == 'revision_information') {

    // Users don't have the option to disable revisions when using Multiversion.
    $form['revision']['#default_value'] = TRUE;
    $form['revision']['#disabled'] = TRUE;
  }
}

/**
 * Prepares a file destination directory.
 *
 * If the directory doesn't exist it tries to create it, if the directory is not
 * writable it tries to make it writable. In case it can't create the directory
 * or make it writable, logs the error message and returns FALSE.
 * When the directory exists and it is writable returns TRUE.
 *
 * @param string $destination
 *
 * @return bool
 */
function multiversion_prepare_file_destination($destination) {
  $dirname = \Drupal::service('file_system')
    ->dirname($destination);
  return file_prepare_directory($dirname, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY);
}

/**
 * Implements hook_menu_links_discovered_alter().
 */
function multiversion_menu_links_discovered_alter(&$links) {

  // Get all custom menu links and set links with the correct ID.
  // The ID format now will be 'menu_link_content:ENTITY_UUID:ENTITY_ID' - we
  // need to change it because we need new entry in the menu_tree table for the
  // same link on different workspaces.
  // The old ID format is 'menu_link_content:ENTITY_UUID'.
  if (\Drupal::moduleHandler()
    ->moduleExists('menu_link_content')) {
    $storage = \Drupal::entityTypeManager()
      ->getStorage('menu_link_content');
    if (\Drupal::service('multiversion.manager')
      ->isEnabledEntityType($storage
      ->getEntityType())) {
      $workspaces = Workspace::loadMultiple();
      foreach ($workspaces as $workspace_id => $workspace) {
        $storage
          ->useWorkspace($workspace_id);
        $menu_link_content_entities = $storage
          ->loadMultiple();
        $new_ids = [];
        $links_to_purge = [];

        /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link_content */
        foreach ($menu_link_content_entities as $menu_link_content) {

          // Unset links with old ID format.
          $uuid = $menu_link_content
            ->uuid();
          $old_id = "menu_link_content:{$uuid}";
          $new_id = "{$old_id}:" . $menu_link_content
            ->id();
          if (isset($links[$old_id])) {
            $links_to_purge[] = $old_id;
            unset($links[$old_id]);
          }
          $new_ids[$old_id] = $new_id;
          if (!isset($links[$new_id])) {
            $links[$new_id] = $menu_link_content
              ->getPluginDefinition();
          }

          // Set a new plugin class tha will handle new ID format.
          $links[$new_id]['class'] = 'Drupal\\multiversion\\Plugin\\Menu\\MenuLinkContent';
        }
        if ($links_to_purge) {
          \Drupal::service('menu.tree_storage')
            ->purgeMultiple($links_to_purge);
        }
        foreach ($links as $id => $link) {
          if (!empty($link['parent']) && in_array($link['parent'], array_keys($new_ids))) {
            $links[$id]['parent'] = $new_ids[$link['parent']];
          }
        }
        $storage
          ->useWorkspace(NULL);
      }
    }
  }
}

/**
 * Implements hook_modules_installed().
 */
function multiversion_modules_installed($modules) {

  // Enable entity types provided by installed modules and supported by
  // Multiversion.
  $entity_type_manager = \Drupal::entityTypeManager();
  $supported_entity_types = \Drupal::configFactory()
    ->getEditable('multiversion.settings')
    ->get('supported_entity_types');
  $supported_entity_types = $supported_entity_types ?: [];
  $entities_to_enable = [];
  foreach ($supported_entity_types as $entity_type_id) {
    $entity_type = $entity_type_manager
      ->getDefinition($entity_type_id, FALSE);
    if (!empty($entity_type) && in_array($entity_type
      ->getProvider(), $modules)) {
      $entities_to_enable[$entity_type_id] = $entity_type;
    }
  }
  if (!empty($entities_to_enable)) {
    \Drupal::service('multiversion.manager')
      ->enableEntityTypes($entities_to_enable);
  }
}