Skip to content
This repository has been archived by the owner on Aug 18, 2024. It is now read-only.

Add a cache tag to invalidate a group's user membership listings #483

Merged
merged 7 commits into from
Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions config/schema/og.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,14 @@ condition.plugin.og_group_type:
type: sequence
sequence:
type: string

block.settings.og_member_count:
type: block_settings
label: 'Group member count block'
mapping:
count_blocked_users:
type: boolean
label: 'Count blocked users'
count_pending_users:
type: boolean
label: 'Count pending users'
32 changes: 32 additions & 0 deletions og.module
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,38 @@ function og_entity_type_alter(array &$entity_types) {
}
}

/**
* Implements hook_theme().
*/
function og_theme($existing, $type, $theme, $path) {
return [
'og_member_count' => [
'variables' => [
pfrenssen marked this conversation as resolved.
Show resolved Hide resolved
'count' => 0,
'membership_states' => [],
'group' => NULL,
],
],
];
}

/**
* Prepares variables for member count templates.
*
* Default template: og-member-count.html.twig.
*
* @param array $variables
* An associative array containing:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array contains also the membership_states key but this is not detailed below.

* - count: An integer representing the number of members in the group.
* - group: The group, which is a content entity.
*/
function template_preprocess_og_member_count(array &$variables) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the reason of this preprocess. We can pass the label directly from the block plugin.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended to demonstrate how and end user would change this block into something useful for them.

/** @var \Drupal\Core\Entity\ContentEntityInterface $group */
$group = $variables['group'];

$variables['group_label'] = $group->label();
}

/**
* Invalidates group content cache tags for the groups this entity belongs to.
*
Expand Down
37 changes: 37 additions & 0 deletions src/Entity/OgMembership.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Drupal\og\Entity;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
Expand Down Expand Up @@ -482,6 +483,42 @@ public function save() {
return $result;
}

/**
* {@inheritdoc}
*/
protected function invalidateTagsOnSave($update) {
parent::invalidateTagsOnSave($update);

// A membership was created or updated: invalidate the membership list cache
// tags of its group. An updated membership may start to appear in a group's
// membership listings because it now meets those listings' filtering
// requirements. A newly created membership may start to appear in listings
// because it did not exist before.
$group = $this->getGroup();
if (!empty($group)) {
$tags = Cache::buildTags(OgMembershipInterface::GROUP_MEMBERSHIP_LIST_CACHE_TAG_PREFIX, $group->getCacheTagsToInvalidate());
Cache::invalidateTags($tags);
}
}

/**
* {@inheritdoc}
*/
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
parent::invalidateTagsOnDelete($entity_type, $entities);

// A membership was deleted: invalidate the list cache tags of its group
// membership lists, so that any lists that contain the membership will be
// recalculated.
$tags = [];
foreach ($entities as $entity) {
if ($group = $entity->getGroup()) {
$tags = Cache::mergeTags(Cache::buildTags(OgMembershipInterface::GROUP_MEMBERSHIP_LIST_CACHE_TAG_PREFIX, $group->getCacheTagsToInvalidate()), $tags);
}
}
Cache::invalidateTags($tags);
}

/**
* {@inheritdoc}
*/
Expand Down
2 changes: 1 addition & 1 deletion src/MembershipManagerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public function getGroups(EntityInterface $entity, $group_type_id = NULL, $group
* Returns the number of groups associated with a given group content entity.
*
* Do not use this to retrieve the group membership count for a user entity.
* Use count(Og::GetEntityGroups()) instead.
* Use count(\Drupal\og\MembershipManager::getUserGroupIds()) instead.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The group content entity for which to count the associated groups.
Expand Down
5 changes: 5 additions & 0 deletions src/OgMembershipInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ interface OgMembershipInterface extends ContentEntityInterface, EntityOwnerInter
*/
const REQUEST_FIELD = 'og_membership_request';

/**
* The prefix that is used to identify group membership list cache tags.
*/
const GROUP_MEMBERSHIP_LIST_CACHE_TAG_PREFIX = 'og-group-membership-list';

/**
* Gets the membership creation timestamp.
*
Expand Down
166 changes: 166 additions & 0 deletions src/Plugin/Block/MemberCountBlock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php

namespace Drupal\og\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\og\MembershipManagerInterface;
use Drupal\og\OgContextInterface;
use Drupal\og\OgMembershipInterface;
use Drupal\og\OgRoleInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides a block that shows the number of members in the current group.
*
* This block is mainly intended to demonstrate the group membership list cache
* tag but can also be used to show the number of members on group pages. The
* way the text is displayed can be changed by overriding the Twig template.
*
* @Block(
* id = "og_member_count",
* admin_label = @Translation("Group member count")
* )
*/
class MemberCountBlock extends BlockBase implements ContainerFactoryPluginInterface {

/**
* The OG context provider.
*
* @var \Drupal\og\OgContextInterface
*/
protected $ogContext;

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

/**
* Constructs a MemberCountBlock object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param string $plugin_definition
* The plugin implementation definition.
* @param \Drupal\og\OgContextInterface $og_context
* The OG context provider.
* @param \Drupal\og\MembershipManagerInterface $membership_manager
* The membership manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, OgContextInterface $og_context, MembershipManagerInterface $membership_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);

$this->ogContext = $og_context;
$this->membershipManager = $membership_manager;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('og.context'),
$container->get('og.membership_manager')
);
}

/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always have to account the parent return value, even in this case that returns an empty array. This is because the parent might return something in the future or at some point we switch to other base class:

return [
  ...
] + parent:: defaultConfiguration();

'count_blocked_users' => FALSE,
'count_pending_users' => FALSE,
];
}

/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$form['count_blocked_users'] = [
'#type' => 'checkbox',
'#title' => $this->t('Count blocked users'),
'#default_value' => $this->configuration['count_blocked_users'],
];

$form['count_pending_users'] = [
'#type' => 'checkbox',
'#title' => $this->t('Count pending users'),
'#default_value' => $this->configuration['count_pending_users'],
];

return $form;
}

/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
foreach (array_keys($this->defaultConfiguration()) as $setting) {
$this->configuration[$setting] = $form_state->getValue($setting);
}
}

/**
* {@inheritdoc}
*/
public function build() {
// Do not render anything if there is no group in the current context.
$group = $this->ogContext->getGroup();
if (empty($group)) {
return [];
}

$states = [OgMembershipInterface::STATE_ACTIVE];

if ($this->configuration['count_blocked_users']) {
$states[] = OgMembershipInterface::STATE_BLOCKED;
}

if ($this->configuration['count_pending_users']) {
$states[] = OgMembershipInterface::STATE_PENDING;
}

$membership_ids = $this->membershipManager->getGroupMembershipIdsByRoleNames($group, [OgRoleInterface::AUTHENTICATED], $states);

return [
'#theme' => 'og_member_count',
'#count' => count($membership_ids),
'#group' => $group,
'#membership_states' => $states,
];
}

/**
* {@inheritdoc}
*/
public function getCacheTags() {
$tags = parent::getCacheTags();

$group = $this->ogContext->getGroup();
if (!empty($group)) {
$tags = Cache::mergeTags(Cache::buildTags(OgMembershipInterface::GROUP_MEMBERSHIP_LIST_CACHE_TAG_PREFIX, $group->getCacheTagsToInvalidate()), $tags);
}

return $tags;
}

/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return Cache::mergeContexts(parent::getCacheContexts(), ['og_group_context']);
}

}
22 changes: 22 additions & 0 deletions templates/og-member-count.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{#
/**
* @file
* Default theme implementation to show the member count of a group.
*
* Available variables:
* - count: The number of members in the group.
* - membership_states: An array of membership states included in the count.
* - group_label: The group label.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The group variable is also available as we don't unset it in template_preprocess_og_member_count(). If we don't need this variable, we probably want to pass the label from the beginning, in the block plugin and remove the template preprocess function. Or we add here the group too.

*
* @see \Drupal\og\Plugin\Block\MemberCountBlock
*
* @ingroup themeable
*/
#}
<p>
{% trans %}
{{ group_label }} has 1 member.
{% plural count %}
{{ group_label }} has {{ count }} members.
{% endtrans %}
</p>
Loading