association-1.0.0-alpha2/modules/association_menu/src/AssociationMenuStorage.php

modules/association_menu/src/AssociationMenuStorage.php
<?php

namespace Drupal\association_menu;

use Drupal\association\Entity\AssociatedEntityInterface;
use Drupal\association\Entity\AssociationInterface;
use Drupal\association_menu\Event\AssociationMenuEvents;
use Drupal\association_menu\Event\MenuLinksAlterEvent;
use Drupal\association_menu\Event\MenuLinksLoadEvent;
use Drupal\Component\Serialization\SerializationInterface;
use Drupal\Core\Access\AccessManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\DatabaseException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Utility\Error;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * The association storage for managing the menu link items.
 */
class AssociationMenuStorage implements AssociationMenuStorageInterface {

  use LoggerChannelTrait;

  /**
   * An array of cached navigation data, already sorted and access checked.
   *
   * @var \Drupal\association_menu\MenuItemInterface[]
   */
  protected $menus = [];

  /**
   * Database connection where the association navigation data is stored.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $db;

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

  /**
   * The access manager.
   *
   * @var \Drupal\Core\Access\AccessManagerInterface
   */
  protected $accessManager;

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

  /**
   * The JSON data serializer.
   *
   * @var \Drupal\Component\Serialization\SerializationInterface
   */
  protected $json;

  /**
   * The association menu cache backend.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cacheBackend;

  /**
   * The account to use for access checks by default.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $account;

  /**
   * Create a new instance of the AssociationMenuStorage manager class.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection to use for storage and retrieval of menu items.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
   *   The access manager.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   *   The event dispatcher.
   * @param \Drupal\Component\Serialization\SerializationInterface $json
   *   The database storage JSON serializer for array data.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   The association menu cache backend.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The account to use for access checks by default.
   */
  public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager, AccessManagerInterface $access_manager, EventDispatcherInterface $event_dispatcher, SerializationInterface $json, CacheBackendInterface $cache_backend, AccountInterface $account) {
    $this->db = $database;
    $this->entityTypeManager = $entity_type_manager;
    $this->accessManager = $access_manager;
    $this->eventDispatcher = $event_dispatcher;
    $this->json = $json;
    $this->cacheBackend = $cache_backend;
    $this->account = $account;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags(AssociationInterface $association): array {
    return [
      'association:menu:' . $association->id(),
    ];
  }

  /**
   * Get the cache key identifier to use for caching the menu data.
   *
   * @return string
   *   A cache key identifier for this menu storage data.
   */
  protected function getCacheId($association): string {
    $assocId = $association instanceof AssociationInterface
      ? $association->id() : $association;

    return 'association_menu:' . $assocId;
  }

  /**
   * {@inheritdoc}
   */
  public function clearCache($assocId = NULL): void {
    if (isset($assocId)) {
      $cid = $this->getCacheId($assocId);
      $this->cacheBackend->delete($cid);
      unset($this->menus[$assocId]);
    }
    else {
      $this->menus = [];
      $this->cacheBackend->invalidateAll();
    }
  }

  /**
   * Ensure the data unserialized and loaded into a MenuItemInterface instance.
   *
   * @param array $values
   *   The raw menu item data (usually from DB), to load and clean up. These
   *   values are loaded into the appropriate menu item instance type.
   *
   * @return \Drupal\association_menu\MenuItemInterface|null
   *   Returns a loaded menu item object from the menu values.
   */
  protected function createItem(array $values): ?MenuItemInterface {
    if (!empty($values['title'])) {
      $values['title'] = unserialize($values['title'], [
        'allowed_classes' => [
          '\Drupal\Component\Render\FormattableMarkup',
          '\Drupal\Core\StringTranslation\TranslatableMarkup',
        ],
      ]);
    }
    $values['options'] = !empty($values['options']) ? $this->json->decode($values['options']) : [];

    if (!empty($values['entity'])) {
      [$entityType, $entityId] = explode(':', $values['entity'], 2);

      if ($entityType && $entityId) {
        $entity = $this->entityTypeManager
          ->getStorage($entityType)
          ->load($entityId);

        if ($entity) {
          return new AssociatedEntityMenuItem($values, $entity);
        }
      }
    }
    elseif (!empty($values['route'])) {
      $route = $this->json->decode($values['route']);

      if (!empty($route['route_name'])) {
        return new RoutedMenuItem($values, $route['route_name'], $route['route_parameters']);
      }
    }
    elseif (!empty($values['uri'])) {
      return new UriMenuItem($values, $values['uri']);
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getMenu(AssociationInterface $association, AccountInterface $account = NULL): array {
    $assocId = $association->id();
    $account = $account ?? $this->account;

    // Fetch and build menu items if they have not already been retrieved
    // recently for the requested user.
    $cid = $this->getCacheId($association);
    $cache = new CacheableMetadata();

    if ($cached = $this->cacheBackend->get($cid)) {
      $data = $cached->data;
      $menuItems = $data['items'];
      $cache->setCacheTags($data['cache']['tags'] ?? []);
      $cache->setCacheContexts($data['cache']['contexts'] ?? []);
    }
    else {
      $cache->addCacheTags($this->getCacheTags($association));

      $filters = ['enabled' => static::ITEM_ENABLED];
      $menuItems = $this->getMenuItems($association, $filters, TRUE);

      // Apply cache metadata from menu items with associated entities.
      foreach ($menuItems as $item) {
        if ($item instanceof AssociatedEntityMenuItem) {
          $entity = $item->getEntity();
          $cache->addCacheableDependency($entity);
        }
      }

      $loadEvent = new MenuLinksLoadEvent($menuItems, $association, $cache);
      // Per discussion https://github.com/phpstan/phpstan-symfony/issue/59
      // @phpstan-ignore-next-line
      $this->eventDispatcher->dispatch($loadEvent, AssociationMenuEvents::MENU_LINKS_LOAD);

      // Cache the menu items and cache data into menu item storage.
      $cacheData = [];
      $cacheData['items'] = $menuItems;
      $cacheData['cache'] = [
        'tags' => $cache->getCacheTags(),
        'contexts' => $cache->getCacheContexts(),
      ];

      $this->cacheBackend->set($cid, $cacheData, Cache::PERMANENT, $cache->getCacheTags());
    }

    // Allow altering of the menu links, this allows for conditional items
    // to be added, as these changes are not cached.
    $alterEvent = new MenuLinksAlterEvent($menuItems, $association, $account, $cache);
    // Per discussion https://github.com/phpstan/phpstan-symfony/issue/59
    // @phpstan-ignore-next-line
    $this->eventDispatcher->dispatch($alterEvent, AssociationMenuEvents::MENU_LINKS_ALTER);

    foreach ($menuItems as $menuId => $item) {
      try {
        $url = $item->getUrl();

        if ($url && $url->isRouted()) {
          $routeName = $url->getRouteName();
          $routeParams = $url->getRouteParameters();
          $access = $this->accessManager->checkNamedRoute($routeName, $routeParams, $account, TRUE);

          // Capture the menu link access, and any caching data that goes with
          // that access resolution. We still need this menu item even if it
          // is not accessible, for menu tree building.
          $item->setAccess($access->isAllowed());
          $cache->addCacheableDependency($access);
        }
        else {
          $item->setAccess(TRUE);
        }
      }
      catch (\Exception $e) {
        unset($menuItems[$menuId]);
        Error::logException($this->getLogger('association_menu'), $e);
      }
    }

    return [
      'id' => $assocId,
      'cache' => $cache,
      'items' => $menuItems,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMenu(AssociationInterface $association): void {
    $assocId = $association->id();

    $this->db
      ->delete(static::TABLE_NAME)
      ->condition('association', $assocId)
      ->execute();

    $this->clearCache($assocId);
    Cache::invalidateTags($this->getCacheTags($association));
  }

  /**
   * {@inheritdoc}
   */
  public function getMenuItem(AssociationInterface $association, $menu_item_id): MenuItemInterface {
    $values = $this->db
      ->select(static::TABLE_NAME, 'nav')
      ->fields('nav')
      ->condition('id', $menu_item_id)
      ->condition('association', $association->id())
      ->execute()
      ->fetchAssoc();

    if ($values) {
      return $this->createItem($values);
    }

    // Report that the requested menu item isn't available.
    $msg = sprintf('Unable to find menu item ID: %s for Association ID: %s', $menu_item_id, $association->id());
    throw new \InvalidArgumentException($msg);
  }

  /**
   * {@inheritdoc}
   */
  public function getMenuItems(AssociationInterface $association, array $filters = [], $flatten = FALSE): array {
    $menuItems = [];

    try {
      $query = $this->db
        ->select(static::TABLE_NAME, 'nav')
        ->fields('nav')
        ->condition('association', $association->id())
        ->orderBy('depth', 'ASC')
        ->orderBy('parent', 'ASC')
        ->orderBy('weight', 'ASC');

      // Apply any additional filters as required.
      foreach ($filters as $field => $value) {
        $query->condition($field, $value);
      }

      $rs = $query->execute();
      $rs->setFetchMode(\PDO::FETCH_ASSOC);
      foreach ($rs as $values) {
        if ($item = $this->createItem($values)) {
          $menuItems[$item->id()] = $item;
        }
      }
    }
    catch (DatabaseException $e) {
      // Issue with fetching items from the database.
    }

    // If $flatten is FALSE, build the menu into a tree structure.
    if (!$flatten) {
      $tree = [];

      foreach ($menuItems as $id => $item) {
        $parent = $item->getParentId();

        if ($parent && !empty($menuItems[$parent])) {
          $menuItems[$parent]->getChildren()[$id] = $item;
        }
        else {
          $tree[$id] = $item;
        }
      }
      return $tree;
    }

    return $menuItems;
  }

  /**
   * {@inheritdoc}
   */
  public function saveMenuItem(AssociationInterface $association, array $values): void {
    $assocId = $association->id();
    $fields = [
      'title' => !empty($values['title']) ? serialize($values['title']) : NULL,
      'options' => $this->json->encode($values['options'] ?? []),
    ];

    if (isset($values['enabled'])) {
      $fields['enabled'] = (bool) $values['enabled'];
    }
    if (isset($values['expanded'])) {
      $fields['expanded'] = (bool) $values['expanded'];
    }

    if (empty($values['entity'])) {
      if (!empty($values['route']['route_name'])) {
        $values['route'] += ['route_parameters' => []];
        $fields['route'] = $this->json->encode($values['route']);
        $fields['uri'] = NULL;
      }
      elseif (!empty($values['uri'])) {
        $fields['uri'] = (string) $values['uri'];
        $fields['route'] = NULL;
      }
      else {
        // If not valid route or URI data is available, then this menu link
        // is invalid. A valid route "<nolink>" should be used if the menu item
        // intentionally has no URL link value.
        throw new \InvalidArgumentException('Menu item is missing URL data and cannot be saved.');
      }
    }

    if (empty($values['id'])) {
      $fields['association'] = $assocId;
      $this->db
        ->insert(static::TABLE_NAME)
        ->fields($fields)
        ->execute();
    }
    else {
      $this->db
        ->update(static::TABLE_NAME)
        ->fields($fields)
        ->condition('association', $assocId)
        ->condition('id', $values['id'])
        ->execute();
    }

    $this->clearCache($assocId);
    Cache::invalidateTags($this->getCacheTags($association));
  }

  /**
   * {@inheritdoc}
   */
  public function deleteMenuItem(AssociationInterface $association, $menu_item_id): bool {
    $assocId = $association->id();

    // Only delete if the association owns the menu item requested, and the
    // link does not have entity data (is associated entity link).
    $successful = (bool) $this->db
      ->delete(static::TABLE_NAME)
      ->condition('association', $assocId)
      ->condition('id', $menu_item_id)
      ->isNull('entity')
      ->execute();

    if ($successful) {
      $this->clearCache($assocId);
      Cache::invalidateTags($this->getCacheTags($association));
    }
    return $successful;
  }

  /**
   * {@inheritdoc}
   */
  public function updateMenuTree(AssociationInterface $association, array $menu_items): void {
    $assocId = $association->id();

    // Only allows the update of the menu tree related data. This prevents
    // accidental data from altering the URL or URL options unintentionally.
    // Use static::saveMenuItem() to save link and attributes.
    $allowedFields = [
      'parent' => 0,
      'enabled' => TRUE,
      'expanded' => TRUE,
      'depth' => TRUE,
      'weight' => 0,
    ];

    foreach ($menu_items as $item) {
      $fields = array_intersect_key($item, $allowedFields);

      $this->db
        ->update(static::TABLE_NAME)
        ->fields($fields)
        ->condition('id', $item['id'])
        ->condition('association', $assocId)
        ->execute();
    }

    $this->clearCache($assocId);
    Cache::invalidateTags($this->getCacheTags($association));
  }

  /**
   * {@inheritdoc}
   */
  public function addAssociated(AssociatedEntityInterface $entity, array $options = []): void {
    if ($association = $entity->getAssociation()) {
      $assocId = $association->id();
      $type = $entity->getEntityTypeId();

      // Only insert if not already included as a menu item.
      $this->db
        ->merge(static::TABLE_NAME)
        ->keys([
          'association' => $assocId,
          'entity' => $type . ':' . $entity->id(),
        ])
        ->insertFields([
          'association' => $assocId,
          'entity' => $type . ':' . $entity->id(),
          'enabled' => static::ITEM_ENABLED,
          'options' => $this->json->encode($options),
        ])
        ->execute();

      $this->clearCache($assocId);
      Cache::invalidateTags($this->getCacheTags($association));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function removeAssociated(AssociatedEntityInterface $entity): void {
    $this->db
      ->delete(static::TABLE_NAME)
      ->condition('entity', $entity->getEntityTypeId() . ':' . $entity->id())
      ->execute();

    if ($association = $entity->getAssociation()) {
      $this->clearCache($association->id());
      Cache::invalidateTags($this->getCacheTags($association));
    }
  }

}

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

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