You are here

class OgRoleCacheContext in Organic groups 8

Defines a cache context service for the OG roles of the current user.

This cache context allows to cache render elements that vary by the role of the user within the available group(s). This is useful for elements that for example display information that is only intended for group members or administrators.

A user might have multiple roles in a group, this is also taken into account when calculating the cache context key.

Since the user might be a member of a large number of groups this cache context key is presented as a hashed value.

Cache context ID: 'og_role'

Hierarchy

Expanded class hierarchy of OgRoleCacheContext

2 files declare their use of OgRoleCacheContext
OgRoleCacheContextTest.php in tests/src/Unit/Cache/Context/OgRoleCacheContextTest.php
OgRoleCacheContextTest.php in tests/src/Kernel/Cache/Context/OgRoleCacheContextTest.php
1 string reference to 'OgRoleCacheContext'
og.services.yml in ./og.services.yml
og.services.yml
1 service uses OgRoleCacheContext
cache_context.og_role in ./og.services.yml
Drupal\og\Cache\Context\OgRoleCacheContext

File

src/Cache/Context/OgRoleCacheContext.php, line 37

Namespace

Drupal\og\Cache\Context
View source
class OgRoleCacheContext extends UserCacheContextBase implements CacheContextInterface {

  /**
   * The string to return when no context is found.
   */
  const NO_CONTEXT = 'none';

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

  /**
   * The membership manager service.
   *
   * @var \Drupal\og\MembershipManagerInterface
   */
  protected $membershipManager;

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

  /**
   * The private key service.
   *
   * @var \Drupal\Core\PrivateKey
   */
  protected $privateKey;

  /**
   * An array of cached cache context key hashes.
   *
   * @var string[]
   */
  protected $hashes = [];

  /**
   * {@inheritdoc}
   */
  public static function getLabel() {
    return new TranslatableMarkup('OG role');
  }

  /**
   * Constructs a new UserCacheContextBase class.
   *
   * @param \Drupal\Core\Session\AccountInterface $user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\og\MembershipManagerInterface $membershipManager
   *   The membership manager service.
   * @param \Drupal\Core\Database\Connection $database
   *   The active database connection.
   * @param \Drupal\Core\PrivateKey $privateKey
   *   The private key service.
   */
  public function __construct(AccountInterface $user, EntityTypeManagerInterface $entityTypeManager, MembershipManagerInterface $membershipManager, Connection $database, PrivateKey $privateKey) {
    parent::__construct($user);
    $this->entityTypeManager = $entityTypeManager;
    $this->membershipManager = $membershipManager;
    $this->database = $database;
    $this->privateKey = $privateKey;
  }

  /**
   * {@inheritdoc}
   */
  public function getContext() {

    // Due to cacheability metadata bubbling this can be called often. Only
    // compute the hash once.
    if (empty($this->hashes[$this->user
      ->id()])) {

      // If the memberships are stored in a SQL database, use a fast SELECT
      // query to retrieve the membership data. If not, fall back to loading
      // the full membership entities.
      $storage = $this->entityTypeManager
        ->getStorage('og_membership');
      $memberships = $storage instanceof SqlContentEntityStorage ? $this
        ->getMembershipsFromDatabase() : $this
        ->getMembershipsFromEntities();

      // Sort the memberships, so that the same key can be generated, even if
      // the memberships were defined in a different order.
      ksort($memberships);
      foreach ($memberships as &$groups) {
        ksort($groups);
        foreach ($groups as &$role_names) {
          sort($role_names);
        }
      }

      // If the user is not a member of any groups, return a unique key.
      $this->hashes[$this->user
        ->id()] = empty($memberships) ? self::NO_CONTEXT : $this
        ->hash(serialize($memberships));
    }
    return $this->hashes[$this->user
      ->id()];
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheableMetadata() {
    return new CacheableMetadata();
  }

  /**
   * Hashes the given string.
   *
   * @param string $identifier
   *   The string to be hashed.
   *
   * @return string
   *   The hash.
   */
  protected function hash($identifier) {
    return hash('sha256', $this->privateKey
      ->get() . Settings::getHashSalt() . $identifier);
  }

  /**
   * Returns membership information by performing a database query.
   *
   * This method retrieves the membership data by doing a direct SELECT query on
   * the membership database. This is very fast but can only be done on SQL
   * databases since the query requires a JOIN between two tables.
   *
   * @return array[][]
   *   An array containing membership information for the current user. The data
   *   is in the format [$entity_type_id][$entity_id][$role_name].
   */
  protected function getMembershipsFromDatabase() : array {
    $storage = $this->entityTypeManager
      ->getStorage('og_membership');
    if (!$storage instanceof SqlContentEntityStorage) {
      throw new \LogicException('Can only retrieve memberships directly from SQL databases.');
    }

    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $storage
      ->getTableMapping();
    $base_table = $table_mapping
      ->getBaseTable();
    $role_table = $table_mapping
      ->getFieldTableName('roles');
    $query = $this->database
      ->select($base_table, 'm');
    $query
      ->leftJoin($role_table, 'r', 'm.id = r.entity_id');
    $query
      ->fields('m', [
      'entity_type',
      'entity_bundle',
      'entity_id',
    ]);
    $query
      ->fields('r', [
      'roles_target_id',
    ]);
    $query
      ->condition('m.uid', $this->user
      ->id());
    $query
      ->condition('m.state', OgMembershipInterface::STATE_ACTIVE);
    $memberships = [];
    foreach ($query
      ->execute() as $row) {
      $entity_type_id = $row->entity_type;
      $entity_bundle_id = $row->entity_bundle;
      $entity_id = $row->entity_id;
      $role_name = $row->roles_target_id;

      // If the role name is empty this is a regular authenticated user. If it
      // is set we can derive the role name from the role ID.
      if (empty($role_name)) {
        $role_name = OgRoleInterface::AUTHENTICATED;
      }
      else {
        $pattern = preg_quote("{$entity_type_id}-{$entity_bundle_id}-");
        preg_match("/{$pattern}(.+)/", $row->roles_target_id, $matches);
        $role_name = $matches[1];
      }
      $memberships[$entity_type_id][$entity_id][] = $role_name;
    }
    return $memberships;
  }

  /**
   * Returns membership information by iterating over membership entities.
   *
   * This method uses pure Entity API methods to retrieve the data. This is slow
   * but also works with NoSQL databases.
   *
   * @return array[][]
   *   An array containing membership information for the current user. The data
   *   is in the format [$entity_type_id][$entity_id][$role_name].
   */
  protected function getMembershipsFromEntities() : array {
    $memberships = [];
    foreach ($this->membershipManager
      ->getMemberships($this->user
      ->id()) as $membership) {

      // Derive the role names from the role IDs. This is faster than loading
      // the OgRole object from the membership.
      $role_names = array_map(function (string $role_id) use ($membership) : string {
        $pattern = preg_quote("{$membership->getGroupEntityType()}-{$membership->getGroupBundle()}-");
        preg_match("/{$pattern}(.+)/", $role_id, $matches);
        return $matches[1];
      }, $membership
        ->getRolesIds());
      if ($role_names) {
        $memberships[$membership
          ->getGroupEntityType()][$membership
          ->getGroupId()] = $role_names;
      }
    }
    return $memberships;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
OgRoleCacheContext::$database protected property The active database connection.
OgRoleCacheContext::$entityTypeManager protected property The entity type manager.
OgRoleCacheContext::$hashes protected property An array of cached cache context key hashes.
OgRoleCacheContext::$membershipManager protected property The membership manager service.
OgRoleCacheContext::$privateKey protected property The private key service.
OgRoleCacheContext::getCacheableMetadata public function Gets the cacheability metadata for the context. Overrides CacheContextInterface::getCacheableMetadata
OgRoleCacheContext::getContext public function Returns the string representation of the cache context. Overrides CacheContextInterface::getContext
OgRoleCacheContext::getLabel public static function Returns the label of the cache context. Overrides CacheContextInterface::getLabel
OgRoleCacheContext::getMembershipsFromDatabase protected function Returns membership information by performing a database query.
OgRoleCacheContext::getMembershipsFromEntities protected function Returns membership information by iterating over membership entities.
OgRoleCacheContext::hash protected function Hashes the given string.
OgRoleCacheContext::NO_CONTEXT constant The string to return when no context is found.
OgRoleCacheContext::__construct public function Constructs a new UserCacheContextBase class. Overrides UserCacheContextBase::__construct
UserCacheContextBase::$user protected property The account object.