og-8.x-1.x-dev/src/GroupTypeManager.php
src/GroupTypeManager.php
<?php
declare(strict_types=1);
namespace Drupal\og;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\og\Event\GroupCreationEvent;
use Drupal\og\Event\GroupCreationEventInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* A manager to keep track of which entity type/bundles are OG group enabled.
*/
class GroupTypeManager implements GroupTypeManagerInterface {
/**
* The key used to identify the cached version of the group relation map.
*/
const GROUP_RELATION_MAP_CACHE_KEY = 'og.group_manager.group_relation_map';
/**
* The OG settings configuration key.
*
* @var string
*/
const SETTINGS_CONFIG_KEY = 'og.settings';
/**
* The OG group settings config key.
*
* @var string
*/
const GROUPS_CONFIG_KEY = 'groups';
/**
* A map of entity types and bundles.
*
* Do not access this property directly, use $this->getGroupMap() instead.
*
* @var array<array-key, list<string>>
*/
protected array $groupMap;
/**
* A map of group and group content relations.
*
* Do not access this property directly, use $this->getGroupRelationMap()
* instead.
*
* @var array<array-key, array<array-key, array<array-key, list<string>>>>
* An associative array representing group and group content relations.
*
* This mapping is in the following format:
* @code
* [
* 'group_entity_type_id' => [
* 'group_bundle_id' => [
* 'group_content_entity_type_id' => [
* 'group_content_bundle_id',
* ],
* ],
* ],
* ]
* @endcode
*/
protected array $groupRelationMap = [];
public function __construct(
protected readonly ConfigFactoryInterface $configFactory,
protected readonly EntityTypeManagerInterface $entityTypeManager,
protected readonly EntityTypeBundleInfoInterface $entityTypeBundleInfo,
protected readonly EventDispatcherInterface $eventDispatcher,
#[Autowire(service: 'cache.data')]
protected readonly CacheBackendInterface $cache,
protected readonly PermissionManagerInterface $permissionManager,
protected readonly OgRoleManagerInterface $ogRoleManager,
protected readonly RouteBuilderInterface $routeBuilder,
protected readonly OgGroupAudienceHelperInterface $groupAudienceHelper,
) {}
/**
* {@inheritdoc}
*/
public function isGroup($entity_type_id, $bundle) {
$group_map = $this->getGroupMap();
return isset($group_map[$entity_type_id]) && in_array($bundle, $group_map[$entity_type_id]);
}
/**
* {@inheritdoc}
*/
public function isGroupContent($entity_type_id, $bundle) {
// To avoid insanity, a group membership cannot be a group or group content.
if ($entity_type_id === 'og_membership') {
return FALSE;
}
return $this->groupAudienceHelper->hasGroupAudienceField($entity_type_id, $bundle);
}
/**
* {@inheritdoc}
*/
public function getGroupBundleIdsByEntityType($entity_type_id) {
$group_map = $this->getGroupMap();
return $group_map[$entity_type_id] ?? [];
}
/**
* {@inheritdoc}
*/
public function getAllGroupContentBundleIds() {
$bundles = [];
foreach ($this->getGroupRelationMap() as $group_bundle_ids) {
foreach ($group_bundle_ids as $group_content_entity_type_ids) {
foreach ($group_content_entity_type_ids as $group_content_entity_type_id => $group_content_bundle_ids) {
$bundles[$group_content_entity_type_id] = array_merge($bundles[$group_content_entity_type_id] ?? [], $group_content_bundle_ids);
}
}
}
return $bundles;
}
/**
* {@inheritdoc}
*/
public function getAllGroupContentBundlesByEntityType($entity_type_id) {
$bundles = $this->getAllGroupContentBundleIds();
if (!isset($bundles[$entity_type_id])) {
throw new \InvalidArgumentException("The '$entity_type_id' entity type has no group content bundles.");
}
return $bundles[$entity_type_id];
}
/**
* {@inheritdoc}
*/
public function getGroupBundleIdsByGroupContentBundle($group_content_entity_type_id, $group_content_bundle_id) {
$bundles = [];
foreach ($this->groupAudienceHelper->getAllGroupAudienceFields($group_content_entity_type_id, $group_content_bundle_id) as $field) {
$group_entity_type_id = $field->getSetting('target_type');
$handler_settings = $field->getSetting('handler_settings');
$group_bundle_ids = !empty($handler_settings['target_bundles']) ? $handler_settings['target_bundles'] : [];
// If the group bundles are empty, it means that all bundles are
// referenced.
if (empty($group_bundle_ids)) {
$group_bundle_ids = $this->getGroupMap()[$group_entity_type_id];
}
foreach ($group_bundle_ids as $group_bundle_id) {
$bundles[$group_entity_type_id][$group_bundle_id] = $group_bundle_id;
}
}
return $bundles;
}
/**
* {@inheritdoc}
*/
public function getGroupContentBundleIdsByGroupBundle($group_entity_type_id, $group_bundle_id) {
$group_relation_map = $this->getGroupRelationMap();
return $group_relation_map[$group_entity_type_id][$group_bundle_id] ?? [];
}
/**
* {@inheritdoc}
*/
public function addGroup($entity_type_id, $bundle_id) {
// To avoid insanity, a group membership cannot be a group or group content.
if ($entity_type_id === 'og_membership') {
throw new \InvalidArgumentException("The '$entity_type_id' type cannot be a group.");
}
// Throw an error if the entity type is already defined as a group.
if ($this->isGroup($entity_type_id, $bundle_id)) {
throw new \InvalidArgumentException("The '$entity_type_id' of type '$bundle_id' is already a group.");
}
$editable = $this->configFactory->getEditable('og.settings');
$groups = $editable->get('groups');
$groups[$entity_type_id][] = $bundle_id;
// @todo Key by bundle ID instead?
$groups[$entity_type_id] = array_unique($groups[$entity_type_id]);
$editable->set('groups', $groups);
$editable->save();
// Trigger an event upon the new group creation.
$event = new GroupCreationEvent($entity_type_id, $bundle_id);
$this->eventDispatcher->dispatch($event, GroupCreationEventInterface::EVENT_NAME);
$this->ogRoleManager->createPerBundleRoles($entity_type_id, $bundle_id);
$this->refreshGroupMap();
// Routes will need to be rebuilt.
$this->routeBuilder->setRebuildNeeded();
}
/**
* {@inheritdoc}
*/
public function removeGroup($entity_type_id, $bundle_id) {
$editable = $this->configFactory->getEditable('og.settings');
$groups = $editable->get('groups');
if (isset($groups[$entity_type_id])) {
$search_key = array_search($bundle_id, $groups[$entity_type_id]);
if ($search_key !== FALSE) {
unset($groups[$entity_type_id][$search_key]);
}
// Clean up entity types that have become empty.
$groups = array_filter($groups);
// Only update and refresh the map if a key was found and unset.
$editable->set('groups', $groups);
// Also clear any configured default membership type for this group
// bundle to keep programmatic removal consistent with the UI flow.
$group_membership_types = $editable->get('group_membership_types') ?? [];
unset($group_membership_types["$entity_type_id:$bundle_id"]);
$editable->set('group_membership_types', $group_membership_types);
$editable->save();
$this->resetGroupMap();
// Routes will need to be rebuilt.
$this->routeBuilder->setRebuildNeeded();
}
}
/**
* {@inheritdoc}
*/
public function getGroupDefaultMembershipType($entity_type_id, $bundle_id): string {
$config = $this->configFactory->get('og.settings');
$group_membership_types = $config->get('group_membership_types') ?? [];
$membership_type_id = $group_membership_types["$entity_type_id:$bundle_id"] ?? OgMembershipInterface::TYPE_DEFAULT;
// Validate that the membership type actually exists. If it was deleted,
// fall back to the default type.
if ($membership_type_id !== OgMembershipInterface::TYPE_DEFAULT) {
$membership_type = $this->entityTypeManager->getStorage('og_membership_type')
->load($membership_type_id);
if (!$membership_type) {
// The configured membership type doesn't exist. Fall back to default.
$membership_type_id = OgMembershipInterface::TYPE_DEFAULT;
}
}
return $membership_type_id;
}
/**
* {@inheritdoc}
*/
public function setGroupDefaultMembershipType($entity_type_id, $bundle_id, $membership_type_id): void {
$editable = $this->configFactory->getEditable('og.settings');
$group_membership_types = $editable->get('group_membership_types') ?? [];
$group_membership_types["$entity_type_id:$bundle_id"] = $membership_type_id;
$editable->set('group_membership_types', $group_membership_types);
$editable->save();
}
/**
* {@inheritdoc}
*/
public function removeGroupDefaultMembershipType($entity_type_id, $bundle_id): void {
$editable = $this->configFactory->getEditable('og.settings');
$group_membership_types = $editable->get('group_membership_types') ?? [];
if (isset($group_membership_types["$entity_type_id:$bundle_id"])) {
unset($group_membership_types["$entity_type_id:$bundle_id"]);
$editable->set('group_membership_types', $group_membership_types);
$editable->save();
}
}
/**
* {@inheritdoc}
*/
public function reset() {
$this->resetGroupMap();
$this->resetGroupRelationMap();
}
/**
* {@inheritdoc}
*/
public function resetGroupMap() {
$this->groupMap = [];
}
/**
* {@inheritdoc}
*/
public function resetGroupRelationMap() {
$this->groupRelationMap = [];
$this->cache->delete(self::GROUP_RELATION_MAP_CACHE_KEY);
}
/**
* {@inheritdoc}
*/
public function getGroupMap() {
if (empty($this->groupMap)) {
$this->refreshGroupMap();
}
return $this->groupMap;
}
/**
* Returns the group relation map.
*
* @return array
* The group relation map.
*/
protected function getGroupRelationMap() {
if (empty($this->groupRelationMap)) {
$this->populateGroupRelationMap();
}
return $this->groupRelationMap;
}
/**
* Refreshes the groupMap property with currently configured groups.
*/
protected function refreshGroupMap() {
$group_map = $this->configFactory->get(static::SETTINGS_CONFIG_KEY)->get(static::GROUPS_CONFIG_KEY);
// To avoid insanity, a group membership cannot be a group or group content.
if (is_array($group_map)) {
unset($group_map['og_membership']);
}
$this->groupMap = !empty($group_map) ? $group_map : [];
}
/**
* Populates the map of relations between group types and group content types.
*/
protected function populateGroupRelationMap(): void {
// Retrieve a cached version of the map if it exists.
if ($cached_map = $this->getCachedGroupRelationMap()) {
$this->groupRelationMap = $cached_map;
return;
}
$this->groupRelationMap = [];
$user_bundles = $this->entityTypeManager->getDefinition('user')->getKey('bundle') ?: ['user'];
foreach ($this->entityTypeBundleInfo->getAllBundleInfo() as $group_content_entity_type_id => $bundles) {
foreach ($bundles as $group_content_bundle_id => $bundle_info) {
if (in_array($group_content_bundle_id, $user_bundles)) {
// User is not a group content per se. Remove it.
continue;
}
foreach ($this->getGroupBundleIdsByGroupContentBundle($group_content_entity_type_id, $group_content_bundle_id) as $group_entity_type_id => $group_bundle_ids) {
foreach ($group_bundle_ids as $group_bundle_id) {
$this->groupRelationMap[$group_entity_type_id][$group_bundle_id][$group_content_entity_type_id][$group_content_bundle_id] = $group_content_bundle_id;
}
}
}
}
// Cache the map.
$this->cache->set(self::GROUP_RELATION_MAP_CACHE_KEY, $this->groupRelationMap);
}
/**
* Returns the group relation map from the cache.
*
* @return array|null
* An associative array representing group and group content relations, or
* NULL if the group relation map was not found in the cache.
*/
protected function getCachedGroupRelationMap(): ?array {
return $this->cache->get(self::GROUP_RELATION_MAP_CACHE_KEY)->data ?? NULL;
}
}
