wse-1.0.x-dev/modules/wse_menu/src/WseMenuTreeStorage.php

modules/wse_menu/src/WseMenuTreeStorage.php
<?php

namespace Drupal\wse_menu;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\Core\Menu\MenuTreeStorage;
use Drupal\workspaces\WorkspaceAssociationInterface;
use Drupal\workspaces\WorkspaceInterface;
use Drupal\workspaces\WorkspaceManagerInterface;

/**
 * Overrides the menu tree storage to provide workspace-specific menu trees.
 */
class WseMenuTreeStorage extends MenuTreeStorage implements WseMenuTreeStorageInterface {

  /**
   * The prefix for workspace-specific menu tree tables.
   */
  const TABLE_PREFIX = 'wse_menu_tree_';

  /**
   * The original menu tree table name.
   */
  protected string $originalTable;

  /**
   * Constructor.
   */
  public function __construct(
    protected WorkspaceManagerInterface $workspaceManager,
    protected WorkspaceAssociationInterface $workspaceAssociation,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected ModuleHandlerInterface $moduleHandler,
    Connection $connection,
    CacheBackendInterface $menu_cache_backend,
    CacheTagsInvalidatorInterface $cache_tags_invalidator,
    string $table,
    array $options = [],
  ) {
    parent::__construct($connection, $menu_cache_backend, $cache_tags_invalidator, $table, $options);
    $this->originalTable = $table;

    // @todo Take care of these points:
    //   - deal with static menu link overrides and views links
  }

  /**
   * {@inheritdoc}
   */
  public function rebuild(array $definitions): void {
    // The menu tree should always be rebuilt without a workspace context.
    $this->workspaceManager->executeOutsideWorkspace(function () use ($definitions) {
      $current_table = $this->table;

      $this->table = $this->originalTable;
      parent::rebuild($definitions);
      $this->table = $current_table;
    });
  }

  /**
   * {@inheritdoc}
   */
  public function load($id) {
    $this->ensureTableOverride();
    return parent::load($id);
  }

  /**
   * {@inheritdoc}
   */
  public function loadMultiple(array $ids) {
    $this->ensureTableOverride();
    return parent::loadMultiple($ids);
  }

  /**
   * {@inheritdoc}
   */
  public function loadByProperties(array $properties) {
    $this->ensureTableOverride();
    return parent::loadByProperties($properties);
  }

  /**
   * {@inheritdoc}
   */
  public function loadByRoute($route_name, array $route_parameters = [], $menu_name = NULL) {
    $this->ensureTableOverride();
    return parent::loadByRoute($route_name, $route_parameters, $menu_name);
  }

  /**
   * {@inheritdoc}
   */
  public function save(array $definition) {
    $this->ensureTableOverride();
    return parent::save($definition);
  }

  /**
   * {@inheritdoc}
   */
  public function delete($id) {
    $this->ensureTableOverride();
    parent::delete($id);
  }

  /**
   * {@inheritdoc}
   */
  public function loadTreeData($menu_name, MenuTreeParameters $parameters) {
    $this->ensureTableOverride();

    // Add the active workspace as a menu tree condition parameter in order to
    // include it in the cache ID.
    if ($active_workspace = $this->workspaceManager->getActiveWorkspace()) {
      $parameters->conditions['workspace'] = $active_workspace->id();
    }
    return parent::loadTreeData($menu_name, $parameters);
  }

  /**
   * {@inheritdoc}
   */
  public function loadAllChildren($id, $max_relative_depth = NULL) {
    $this->ensureTableOverride();
    return parent::loadAllChildren($id, $max_relative_depth);
  }

  /**
   * {@inheritdoc}
   */
  public function getAllChildIds($id) {
    $this->ensureTableOverride();
    return parent::getAllChildIds($id);
  }

  /**
   * {@inheritdoc}
   */
  public function loadSubtreeData($id, $max_relative_depth = NULL) {
    $this->ensureTableOverride();
    return parent::loadSubtreeData($id, $max_relative_depth);
  }

  /**
   * {@inheritdoc}
   */
  public function getRootPathIds($id) {
    $this->ensureTableOverride();
    return parent::getRootPathIds($id);
  }

  /**
   * {@inheritdoc}
   */
  public function getExpanded($menu_name, array $parents) {
    $this->ensureTableOverride();
    return parent::getExpanded($menu_name, $parents);
  }

  /**
   * {@inheritdoc}
   */
  public function getSubtreeHeight($id) {
    $this->ensureTableOverride();
    return parent::getSubtreeHeight($id);
  }

  /**
   * {@inheritdoc}
   */
  public function menuNameInUse($menu_name) {
    $this->ensureTableOverride();
    return parent::menuNameInUse($menu_name);
  }

  /**
   * {@inheritdoc}
   */
  public function getMenuNames() {
    $this->ensureTableOverride();
    return parent::getMenuNames();
  }

  /**
   * {@inheritdoc}
   */
  public function countMenuLinks($menu_name = NULL) {
    $this->ensureTableOverride();
    return parent::countMenuLinks($menu_name);
  }

  /**
   * Ensures that the storage uses the possibly overridden menu tree table.
   */
  protected function ensureTableOverride(): void {
    $current_table = $this->table;

    // Use a workspace-specific menu tree table if needed.
    if ($active_workspace = $this->workspaceManager->getActiveWorkspace()) {
      $this->table = $this->getWorkspaceTableName($active_workspace);
    }
    else {
      $this->table = $this->originalTable;
    }

    // Reset the internal definitions if they were built with a different table.
    if ($this->table != $current_table) {
      $this->resetDefinitions();
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function ensureTableExists(): bool {
    $current_table = $this->table;

    // The parent method must always run with the original table name.
    $this->table = $this->originalTable;
    $return = parent::ensureTableExists();
    $this->table = $current_table;

    return $return;
  }

  /**
   * {@inheritdoc}
   */
  public function rebuildWorkspaceMenuTree(WorkspaceInterface $workspace, bool $replay_changes = TRUE): void {
    $this->ensureTableExists();
    $table_name = $this->getWorkspaceTableName($workspace);

    if ($this->connection->schema()->tableExists($table_name)) {
      $this->connection->schema()->dropTable($table_name);
    }

    $this->connection->schema()->createTable($table_name, static::schemaDefinition());

    $select = $this->connection->select($this->originalTable)
      ->fields($this->originalTable);
    $this->connection->insert($table_name)
      ->from($select)
      ->execute();

    // Replay all the menu tree changes from this workspace.
    if ($replay_changes) {
      $current_table = $this->table;
      $this->table = $table_name;
      $definitions = [];

      $tracked_menu_link_ids = $this->workspaceAssociation
        ->getTrackedEntities($workspace->id(), 'menu_link_content')['menu_link_content'] ?? [];
      if ($tracked_menu_link_ids) {
        /** @var \Drupal\menu_link_content\MenuLinkContentInterface[] $menu_links */
        $menu_links = $this->entityTypeManager
          ->getStorage('menu_link_content')
          ->loadMultipleRevisions(array_keys($tracked_menu_link_ids));
        foreach ($menu_links as $menu_link) {
          $definitions[$menu_link->getPluginId()] = $menu_link->getPluginDefinition();
        }
      }

      // Sort the definitions by menu_name, parent and weight, for fewer
      // recursive calls below.
      $menu_name = array_column($definitions, 'menu_name');
      $parent = array_column($definitions, 'parent');
      $weight = array_column($definitions, 'weight');
      array_multisort($menu_name, SORT_ASC, $parent, SORT_ASC, $weight, SORT_ASC, $definitions);

      foreach ($definitions as $key => $definition) {
        $this->resaveDefinition($definitions, $key);
      }

      // Ensure that trashed menu items are removed from the workspace-specific
      // menu tree.
      if ($this->moduleHandler->moduleExists('trash') && isset($menu_links)) {
        foreach ($menu_links as $menu_link) {
          if (trash_entity_is_deleted($menu_link)) {
            parent::delete($menu_link->getPluginId());
          }
        }
      }

      // Restore the original menu tree table name when we're finished.
      $this->table = $current_table;
    }

    // @todo Investigate whether we can target only workspace-specific caches.
    $this->menuCacheBackend->deleteAll();
  }

  /**
   * Saves a menu definition, ensuring that its parent is saved first.
   *
   * @param array $definitions
   *   An array of menu tree definitions.
   * @param string $key
   *   The ID of the definition to save.
   */
  protected function resaveDefinition(array &$definitions, string $key): void {
    if (isset($definitions[$key])) {
      if (($parent = $definitions[$key]['parent']) && isset($definitions[$parent])) {
        $this->resaveDefinition($definitions, $parent);
      }
      parent::save($definitions[$key]);
      unset($definitions[$key]);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function cleanupWorkspaceMenuTree(WorkspaceInterface $workspace): void {
    $this->connection->schema()->dropTable($this->getWorkspaceTableName($workspace));
  }

  /**
   * {@inheritdoc}
   */
  public function getAllWorkspacesWithMenuTreeOverrides(): array {
    $uuids = [];
    foreach ($this->connection->schema()->findTables(static::TABLE_PREFIX . '%') as $table) {
      $uuids[] = str_replace('_', '-', substr($table, strlen(static::TABLE_PREFIX)));
    }

    if (!$uuids) {
      return [];
    }

    return $this->entityTypeManager
      ->getStorage('workspace')
      ->getQuery()
      ->accessCheck(FALSE)
      ->condition('uuid', $uuids, 'IN')
      ->execute();
  }

  /**
   * Gets the workspace-specific menu tree table name.
   *
   * @param \Drupal\workspaces\WorkspaceInterface $workspace
   *   The workspace entity.
   *
   * @return string
   *   The workspace-specific menu tree table name.
   *
   * @internal
   *   This method is public only to aid in testing.
   */
  public function getWorkspaceTableName(WorkspaceInterface $workspace): string {
    // Workspaces currently have string IDs, but that might change in the
    // future, so it's safer to use its UUID.
    assert(is_string($workspace->uuid()));
    return static::TABLE_PREFIX . str_replace('-', '_', $workspace->uuid());
  }

}

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

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