diff --git a/og.module b/og.module index dcc414364..e8628416d 100755 --- a/og.module +++ b/og.module @@ -329,12 +329,10 @@ function og_theme($existing, $type, $theme, $path) { function og_invalidate_group_content_cache_tags(EntityInterface $entity) { // If group content is created or updated, invalidate the group content cache // tags for each of the groups this group content belongs to. This allows - // group listings to be cached effectively. Also invalidate the tags if the - // group itself changes. The cache tag format is + // group listings to be cached effectively. The cache tag format is // 'og-group-content:{group entity type}:{group entity id}'. $is_group_content = Og::isGroupContent($entity->getEntityTypeId(), $entity->bundle()); - $group_is_updated = Og::isGroup($entity->getEntityTypeId(), $entity->bundle()) && !empty($entity->original); - if ($group_is_updated || $is_group_content) { + if ($is_group_content) { /** @var \Drupal\og\MembershipManagerInterface $membership_manager */ $membership_manager = \Drupal::service('og.membership_manager'); $tags = []; @@ -345,7 +343,7 @@ function og_invalidate_group_content_cache_tags(EntityInterface $entity) { // the tags of the old group(s). /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ $original = !empty($entity->original) ? $entity->original : NULL; - if ($is_group_content && $original) { + if ($original) { /** @var \Drupal\og\OgGroupAudienceHelperInterface $group_audience_helper */ $group_audience_helper = \Drupal::service('og.group_audience_helper'); /** @var \Drupal\Core\Entity\FieldableEntityInterface $original */ diff --git a/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php b/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php index e903f2de1..e69e86495 100644 --- a/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php +++ b/tests/src/Kernel/Entity/CacheInvalidationOnGroupChangeTest.php @@ -1,20 +1,30 @@ installEntitySchema('entity_test'); $this->installEntitySchema('user'); + $this->cache = $this->container->get('cache.default'); + // Add a OG group audience. Og::groupTypeManager()->addGroup('entity_test', 'group'); $settings = [ @@ -61,39 +73,126 @@ protected function setUp() { */ public function testCacheInvalidationOnGroupChange() { // Create two groups. - $group1 = EntityTest::create([ - 'type' => 'group', - 'name' => $this->randomString(), - ]); - $group1->save(); - $group2 = EntityTest::create([ - 'type' => 'group', - 'name' => $this->randomString(), - ]); - $group2->save(); + $groups = []; + for ($i = 0; $i < 2; $i++) { + $groups[$i] = EntityTest::create([ + 'type' => 'group', + 'name' => $this->randomString(), + ]); + $groups[$i]->save(); + } - // Create a group content. + // Create a group content entity that belong to the first group. $group_content = EntityTest::create([ 'type' => 'group_content', 'name' => $this->randomString(), - OgGroupAudienceHelperInterface::DEFAULT_FIELD => $group1->id(), + OgGroupAudienceHelperInterface::DEFAULT_FIELD => $groups[0]->id(), ]); $group_content->save(); - // Cache some arbitrary data tagged with the OG group content tag. - $bin = \Drupal::cache(); - $cid = strtolower($this->randomMachineName()); - $tags = Cache::buildTags('og-group-content', $group1->getCacheTagsToInvalidate()); - $bin->set($cid, $this->randomString(), Cache::PERMANENT, $tags); + // Cache some arbitrary data tagged with the OG group content tags for both + // groups. + $this->populateCache($groups[0]); + $this->populateCache($groups[1]); + // Sanity check, the cached content listings of both groups should be + // populated. + $this->assertCachePopulated($groups[0]); + $this->assertCachePopulated($groups[1]); + + // Change the label of group 1. This should not affect any of the cached + // listings. + $groups[0]->setName($this->randomString())->save(); + $this->assertCachePopulated($groups[0]); + $this->assertCachePopulated($groups[1]); + + // Move the group content from group 1 to group 2. This should invalidate + // the group content list cache tags of both groups. $group_content - ->set(OgGroupAudienceHelperInterface::DEFAULT_FIELD, $group2->id()) + ->set(OgGroupAudienceHelperInterface::DEFAULT_FIELD, $groups[1]->id()) ->save(); // Cache entries tagged with 'og-group-content:entity_type:{$group->id()}' // should have been invalidated at this point because the content members of - // $group1 have changed. - $this->assertFalse($bin->get($cid)); + // both groups have changed. + $this->assertCacheNotPopulated($groups[0]); + $this->assertCacheNotPopulated($groups[1]); + + // Now populate both caches while including the cache tags of the group + // itself. This can happen for example if a listing of group content is + // shown that includes the group name in its content. + $this->populateCache($groups[0], TRUE); + $this->populateCache($groups[1], TRUE); + + // Change the label of group 1. This should invalidate the cache of the + // group content listing for group 1, but not for group 2. + $groups[0]->setName($this->randomString())->save(); + $this->assertCacheNotPopulated($groups[0]); + $this->assertCachePopulated($groups[1]); + } + + /** + * Caches a listing of group content that belongs to the given group. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $group + * The group for which to cache a group content listing. + * @param bool $include_group_cache_tag + * Whether or not the group content listing is tagged with the group's cache + * tags. + */ + protected function populateCache(ContentEntityInterface $group, bool $include_group_cache_tag = FALSE): void { + $cid = $this->generateCid($group); + $tags = Cache::buildTags('og-group-content', $group->getCacheTagsToInvalidate()); + if ($include_group_cache_tag) { + $tags = Cache::mergeTags($tags, $group->getCacheTagsToInvalidate()); + } + $this->cache->set($cid, $this->randomString(), Cache::PERMANENT, $tags); + } + + /** + * Generates a cache ID for a group content listing of the given group. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $group + * The group for which to generate a group content listing cache ID. + * + * @return string + * The cache ID. + */ + protected function generateCid(ContentEntityInterface $group): string { + return implode(':', ['my_group_content_listing', $group->id()]); + } + + /** + * Checks if the group content listing cache for a given group is populated. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $group + * The group for which to perform the check. + */ + protected function assertCachePopulated(ContentEntityInterface $group): void { + $this->assertTrue($this->getCachedData($group)); + } + + /** + * Checks if the group content listing cache for a given group is unpopulated. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $group + * The group for which to perform the check. + */ + protected function assertCacheNotPopulated(ContentEntityInterface $group): void { + $this->assertFalse($this->getCachedData($group)); + } + + /** + * Returns the cached group content listing for a given group, if available. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $group + * The group for which to return the cached group content listing. + * + * @return false|object + * The cache item or FALSE on failure. + */ + protected function getCachedData(ContentEntityInterface $group) { + return $this->cache->get($this->generateCid($group)); } }