og-8.x-1.x-dev/src/MembershipManager.php

src/MembershipManager.php
<?php

declare(strict_types=1);

namespace Drupal\og;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\og\Entity\OgMembership;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Service for managing memberships and group content.
 */
class MembershipManager implements MembershipManagerInterface {

  public function __construct(
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly OgGroupAudienceHelperInterface $groupAudienceHelper,
    protected readonly GroupTypeManagerInterface $groupTypeManager,
    // @todo Replace with correct cache memory backend in #3503948.
    // @see https://www.drupal.org/i/3503948
    #[Autowire(service: 'cache.static')]
    protected CacheBackendInterface $staticCache,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function getUserGroupIds($user_id, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }
    $group_ids = [];

    /** @var \Drupal\og\Entity\OgMembership[] $memberships */
    $memberships = $this->getMemberships($user_id, $states);
    foreach ($memberships as $membership) {
      $group_ids[$membership->getGroupEntityType()][] = $membership->getGroupId();
    }

    return $group_ids;
  }

  /**
   * {@inheritdoc}
   */
  public function getUserGroups($user_id, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    $group_ids = $this->getUserGroupIds($user_id, $states);
    return $this->loadGroups($group_ids);
  }

  /**
   * {@inheritdoc}
   */
  public function getMemberships($user_id, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    // When an empty array is passed, retrieve memberships with all possible
    // states.
    $states = $this->prepareConditionArray($states, OgMembership::ALL_STATES);

    $cid = [
      __METHOD__,
      $user_id,
      implode('|', $states),
    ];
    $cid = implode(':', $cid);

    // Use cached result if it exists.
    if (!$membership_ids = $this->staticCache->get($cid)->data ?? []) {
      $query = $this->entityTypeManager
        ->getStorage('og_membership')
        ->getQuery()
        ->accessCheck()
        ->condition('uid', $user_id)
        ->condition('state', $states, 'IN');

      $membership_ids = $query->execute();
      $this->cacheMembershipIds($cid, $membership_ids);
    }

    return $this->loadMemberships($membership_ids);
  }

  /**
   * {@inheritdoc}
   */
  public function getMembership(EntityInterface $group, $user_id, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    if ($group->isNew()) {
      return NULL;
    }

    // When an empty array is passed, retrieve memberships with all possible
    // states.
    $states = $this->prepareConditionArray($states, OgMembership::ALL_STATES);

    $cid = [
      __METHOD__,
      $group->getEntityTypeId(),
      $group->id(),
      $user_id,
      implode('|', $states),
    ];
    $cid = implode(':', $cid);

    // Use cached result if it exists.
    $membership_ids = $this->staticCache->get($cid)->data ?? [];
    if (!$membership_ids) {
      $query = $this->entityTypeManager
        ->getStorage('og_membership')
        ->getQuery()
        ->accessCheck()
        ->condition('entity_type', $group->getEntityTypeId())
        ->condition('entity_id', $group->id())
        ->condition('uid', $user_id)
        ->condition('state', $states, 'IN');

      $membership_ids = $query->execute();
      $this->cacheMembershipIds($cid, $membership_ids);
    }
    if ($memberships = $this->loadMemberships($membership_ids)) {
      return reset($memberships);
    }

    // No membership matches the request.
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getUserGroupIdsByRoleIds($user_id, array $role_ids, array $states = [OgMembershipInterface::STATE_ACTIVE], bool $require_all_roles = TRUE): array {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    /** @var \Drupal\og\OgMembershipInterface[] $memberships */
    $memberships = $this->getMemberships($user_id, $states);
    $memberships = array_filter($memberships, function (OgMembershipInterface $membership) use ($role_ids, $require_all_roles): bool {
      $membership_roles_ids = $membership->getRolesIds();
      return $require_all_roles ? empty(array_diff($role_ids, $membership_roles_ids)) : !empty(array_intersect($membership_roles_ids, $role_ids));
    });

    $group_ids = [];
    foreach ($memberships as $membership) {
      $group_ids[$membership->getGroupEntityType()][] = $membership->getGroupId();
    }
    return $group_ids;
  }

  /**
   * {@inheritdoc}
   */
  public function getUserGroupsByRoleIds($user_id, array $role_ids, array $states = [OgMembershipInterface::STATE_ACTIVE], bool $require_all_roles = TRUE): array {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    $group_ids = $this->getUserGroupIdsByRoleIds($user_id, $role_ids, $states, $require_all_roles);
    return $this->loadGroups($group_ids);
  }

  /**
   * {@inheritdoc}
   */
  public function getGroupMembershipCount(EntityInterface $group, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if ($group->isNew()) {
      return 0;
    }

    $query = $this->entityTypeManager
      ->getStorage('og_membership')
      ->getQuery()
      ->accessCheck()
      ->condition('entity_id', $group->id());

    if ($states) {
      $query->condition('state', $states, 'IN');
    }

    $query->count();

    return (int) $query->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function getGroupMembershipIdsByRoleNames(EntityInterface $group, array $role_names, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if (empty($role_names)) {
      throw new \InvalidArgumentException('The array of role names should not be empty.');
    }

    if ($group->isNew()) {
      return [];
    }

    // In case the 'member' role is one of the requested roles, we just need to
    // return all memberships. We can safely ignore all other roles.
    $retrieve_all_memberships = FALSE;
    if (in_array(OgRoleInterface::AUTHENTICATED, $role_names)) {
      $retrieve_all_memberships = TRUE;
      $role_names = [OgRoleInterface::AUTHENTICATED];
    }

    $role_names = $this->prepareConditionArray($role_names);
    $states = $this->prepareConditionArray($states, OgMembership::ALL_STATES);

    $cid = [
      __METHOD__,
      $group->getEntityTypeId(),
      $group->id(),
      implode('|', $role_names),
      implode('|', $states),
    ];
    $cid = implode(':', $cid);

    // Only query the database if no cached result exists.
    if (!$membership_ids = $this->staticCache->get($cid)->data ?? []) {
      $entity_type_id = $group->getEntityTypeId();

      $query = $this->entityTypeManager
        ->getStorage('og_membership')
        ->getQuery()
        ->accessCheck()
        ->condition('entity_type', $entity_type_id)
        ->condition('entity_id', $group->id())
        ->condition('state', $states, 'IN');

      if (!$retrieve_all_memberships) {
        $bundle_id = $group->bundle();
        $role_ids = array_map(function ($role_name) use ($entity_type_id, $bundle_id) {
          return implode('-', [$entity_type_id, $bundle_id, $role_name]);
        }, $role_names);

        $query->condition('roles', $role_ids, 'IN');
      }

      $membership_ids = $query->execute();
      $this->cacheMembershipIds($cid, $membership_ids);
    }

    return $membership_ids;
  }

  /**
   * {@inheritdoc}
   */
  public function getGroupMembershipsByRoleNames(EntityInterface $group, array $role_names, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    $ids = $this->getGroupMembershipIdsByRoleNames($group, $role_names, $states);
    return $this->loadMemberships($ids);
  }

  /**
   * {@inheritdoc}
   */
  public function createMembership(EntityInterface $group, UserInterface $user, $membership_type = NULL) {

    if (empty($membership_type)) {
      $membership_type = $this->groupTypeManager->getGroupDefaultMembershipType($group->getEntityTypeId(), $group->bundle());
    }

    /** @var \Drupal\user\UserInterface $user */
    /** @var \Drupal\og\OgMembershipInterface $membership */
    $membership = OgMembership::create(['type' => $membership_type]);
    $membership
      ->setOwner($user)
      ->setGroup($group);

    return $membership;
  }

  /**
   * {@inheritdoc}
   */
  public function getGroupIds(EntityInterface $entity, $group_type_id = NULL, $group_bundle = NULL) {
    // This does not work for user entities.
    if ($entity instanceof UserInterface) {
      throw new \InvalidArgumentException('\Drupal\og\MembershipManager::getGroupIds() cannot be used for user entities. Use \Drupal\og\MembershipManager::getUserGroups() instead.');
    }

    // This should only be called on group content types.
    if (!$this->groupAudienceHelper->hasGroupAudienceField($entity->getEntityTypeId(), $entity->bundle())) {
      throw new \InvalidArgumentException('Can only retrieve group IDs for group content entities.');
    }

    $cid = [
      __METHOD__,
      $entity->getEntityTypeId(),
      $entity->id(),
      $group_type_id,
      $group_bundle,
    ];

    $cid = implode(':', $cid);

    if ($group_ids = $this->staticCache->get($cid)->data ?? []) {
      // Return cached values.
      return $group_ids;
    }

    $group_ids = [];
    $tags = $entity->getCacheTagsToInvalidate();

    $fields = $this->groupAudienceHelper->getAllGroupAudienceFields($entity->getEntityTypeId(), $entity->bundle(), $group_type_id, $group_bundle);
    foreach ($fields as $field) {
      $target_type_id = $field->getFieldStorageDefinition()->getSetting('target_type');
      $target_type_definition = $this->entityTypeManager->getDefinition($target_type_id);

      // Optionally filter by group type.
      if (!empty($group_type_id) && $group_type_id !== $target_type_id) {
        continue;
      }

      $values = $entity->get($field->getName())->getValue();
      if (empty($values[0])) {
        // Entity doesn't reference any groups.
        continue;
      }

      // Compile a list of group target IDs.
      $target_ids = array_filter(array_map(function ($value) {
        return $value['target_id'] ?? NULL;
      }, $entity->get($field->getName())->getValue()));

      if (empty($target_ids)) {
        continue;
      }

      // Query the database to get the actual list of groups. The target IDs may
      // contain groups that no longer exist. Entity reference doesn't clean up
      // orphaned target IDs.
      $entity_type = $this->entityTypeManager->getDefinition($target_type_id);
      $query = $this->entityTypeManager
        ->getStorage($target_type_id)
        ->getQuery()
        // Disable entity access check so fetching the groups related to group
        // content are not affected by the current user. Furthermore, when
        // rebuilding node access and the groups are nodes, we should not try to
        // retrieve node access records which do not exist because the rebuild
        // process has already erased the grants table.
        ->accessCheck(FALSE)
        ->condition($entity_type->getKey('id'), $target_ids, 'IN');

      // Optionally filter by group bundle.
      if (!empty($group_bundle)) {
        $query->condition($entity_type->getKey('bundle'), $group_bundle);
      }

      // Add the list cache tags for the entity type, so that the cache gets
      // invalidated if one of the group entities is deleted.
      $tags = Cache::mergeTags($target_type_definition->getListCacheTags(), $tags);

      $group_ids = NestedArray::mergeDeep($group_ids, [$target_type_id => $query->execute()]);
    }

    $this->staticCache->set($cid, $group_ids, Cache::PERMANENT, $tags);

    return $group_ids;
  }

  /**
   * {@inheritdoc}
   */
  public function getGroups(EntityInterface $entity, $group_type_id = NULL, $group_bundle = NULL) {
    $group_ids = $this->getGroupIds($entity, $group_type_id, $group_bundle);
    return $this->loadGroups($group_ids);
  }

  /**
   * {@inheritdoc}
   */
  public function getGroupCount(EntityInterface $entity, $group_type_id = NULL, $group_bundle = NULL) {
    return array_reduce($this->getGroupIds($entity, $group_type_id, $group_bundle), function ($carry, $item) {
      return $carry + count($item);
    }, 0);
  }

  /**
   * {@inheritdoc}
   */
  public function getGroupContentIds(EntityInterface $entity, array $entity_types = []) {
    $group_content = [];

    // Retrieve the fields which reference our entity type and bundle.
    $query = $this->entityTypeManager
      ->getStorage('field_storage_config')
      ->getQuery()
      ->condition('type', OgGroupAudienceHelperInterface::GROUP_REFERENCE);

    // Optionally filter group content entity types.
    if ($entity_types) {
      $query->condition('entity_type', $entity_types, 'IN');
    }

    /** @var \Drupal\field\FieldStorageConfigInterface[] $fields */
    $storage = $this->entityTypeManager->getStorage('field_storage_config');
    $fields = array_filter($storage->loadMultiple($query->execute()), function (FieldStorageConfigInterface $field) use ($entity) {
      $type_matches = $field->getSetting('target_type') === $entity->getEntityTypeId();
      // If the list of target bundles is empty, it targets all bundles.
      $bundle_matches = empty($field->getSetting('target_bundles')) || in_array($entity->bundle(), $field->getSetting('target_bundles'));
      return $type_matches && $bundle_matches;
    });

    // Compile the group content.
    foreach ($fields as $field) {
      $group_content_entity_type = $field->getTargetEntityTypeId();

      // Group the group content per entity type.
      if (!isset($group_content[$group_content_entity_type])) {
        $group_content[$group_content_entity_type] = [];
      }

      // Query all group content that references the group through this field.
      $results = $this->entityTypeManager
        ->getStorage($group_content_entity_type)
        ->getQuery()
        ->accessCheck()
        ->condition($field->getName() . '.target_id', $entity->id())
        ->execute();

      $group_content[$group_content_entity_type] = array_merge($group_content[$group_content_entity_type], $results);
    }

    return $group_content;
  }

  /**
   * {@inheritdoc}
   */
  public function isMember(EntityInterface $group, $user_id, array $states = [OgMembershipInterface::STATE_ACTIVE]) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    if ($group->isNew()) {
      return FALSE;
    }

    $group_ids = $this->getUserGroupIds($user_id, $states);
    $entity_type_id = $group->getEntityTypeId();
    return !empty($group_ids[$entity_type_id]) && in_array($group->id(), $group_ids[$entity_type_id]);
  }

  /**
   * {@inheritdoc}
   */
  public function isMemberPending(EntityInterface $group, $user_id) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    return $this->isMember($group, $user_id, [OgMembershipInterface::STATE_PENDING]);
  }

  /**
   * {@inheritdoc}
   */
  public function isMemberBlocked(EntityInterface $group, $user_id) {
    if ($user_id instanceof AccountInterface) {
      @trigger_error('Passing an account object is deprecated in og:8.1.0-alpha4 and is removed from og:8.1.0-beta1. Instead pass the user ID as an integer value. See https://github.com/Gizra/og/issues/542', E_USER_DEPRECATED);
      $user_id = $user_id->id();
    }

    return $this->isMember($group, $user_id, [OgMembershipInterface::STATE_BLOCKED]);
  }

  /**
   * Prepares a conditional array for use in a cache identifier and query.
   *
   * This will filter out any duplicate values from the array and sort the
   * values so that a consistent cache identifier can be generated. Optionally
   * it can substitute an empty array with a default value.
   *
   * @param array $value
   *   The array to prepare.
   * @param array|null $default
   *   An optional default value to use in case the passed in value is empty. If
   *   set to NULL this will be ignored.
   *
   * @return array
   *   The prepared array.
   */
  protected function prepareConditionArray(array $value, ?array $default = NULL) {
    // Fall back to the default value if the passed in value is empty and a
    // default value is given.
    if (empty($value) && $default !== NULL) {
      $value = $default;
    }
    sort($value);
    return array_unique($value);
  }

  /**
   * Loads the entities of an associative array of entity IDs.
   *
   * @param array[] $group_ids
   *   An associative array of entity IDs indexed by their entity type ID.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface[][]
   *   An associative array of entities indexed by their entity type ID.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   *   Thrown when the entity type definition of one or more of the passed in
   *   entity types is invalid.
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *   Thrown when one or more of the passed in entity types is not defined.
   */
  protected function loadGroups(array $group_ids): array {
    $groups = [];
    foreach ($group_ids as $entity_type => $ids) {
      $groups[$entity_type] = $this->entityTypeManager->getStorage($entity_type)->loadMultiple($ids);
    }

    return $groups;
  }

  /**
   * Stores the given list of membership IDs in the static cache backend.
   *
   * @param string $cid
   *   The cache ID.
   * @param array $membership_ids
   *   The list of membership IDs to store in the static cache.
   */
  protected function cacheMembershipIds($cid, array $membership_ids) {
    $tags = Cache::buildTags('og_membership', $membership_ids);
    // Also invalidate the list cache tags so that if a new membership is
    // created it will appear in this list.
    $tags = Cache::mergeTags(['og_membership_list'], $tags);
    $this->staticCache->set($cid, $membership_ids, Cache::PERMANENT, $tags);
  }

  /**
   * Returns the full membership entities with the given memberships IDs.
   *
   * @param array $ids
   *   The IDs of the memberships to load.
   *
   * @return \Drupal\og\OgMembershipInterface[]
   *   The membership entities.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   *   Thrown when the entity type definition of one or more of the passed in
   *   entity types is invalid.
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   *   Thrown when one or more of the passed in entity types is not defined.
   */
  protected function loadMemberships(array $ids) {
    if (empty($ids)) {
      return [];
    }

    return $this->entityTypeManager
      ->getStorage('og_membership')
      ->loadMultiple($ids);
  }

}

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

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