og-8.x-1.x-dev/src/Plugin/views/relationship/GroupContentByGroupRelationship.php
src/Plugin/views/relationship/GroupContentByGroupRelationship.php
<?php
declare(strict_types=1);
namespace Drupal\og\Plugin\views\relationship;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\og\GroupTypeManagerInterface;
use Drupal\og\OgGroupAudienceHelperInterface;
use Drupal\views\Attribute\ViewsRelationship;
use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
use Drupal\views\Plugin\ViewsHandlerManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Relates group content to its OG group(s) via any audience fields.
*
* This creates a LEFT JOIN to a derived UNION subquery with columns:
* (entity_id, group_id). Starting from group content, it exposes group fields
* by further joining to the group base table.
*/
#[ViewsRelationship(
id: 'og_group_content_to_group',
)]
class GroupContentByGroupRelationship extends RelationshipPluginBase implements ContainerFactoryPluginInterface {
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
protected readonly EntityTypeManagerInterface $entityTypeManager,
protected readonly OgGroupAudienceHelperInterface $groupAudienceHelper,
protected readonly GroupTypeManagerInterface $groupTypeManager,
protected readonly ViewsHandlerManager $joinManager,
protected readonly Connection $database,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('og.group_audience_helper'),
$container->get('og.group_type_manager'),
$container->get('plugin.manager.views.join'),
$container->get('database'),
);
}
/**
* {@inheritdoc}
*/
public function query() {
// Prefer explicit hints from views data if present.
$group_content_entity_type = $this->definition['entity_type'] ?? NULL;
$group_entity_type = $this->definition['group_entity_type'] ?? NULL;
// Infer implicitly when hints are not provided.
if (!$group_content_entity_type) {
// The relationship is attached on the group content base/data table.
$group_content_entity_type = $this->resolveEntityTypeFromTable($this->table) ?? NULL;
}
if (!$group_entity_type && isset($this->definition['base'])) {
// The 'base' in the relationship definition points to the group table.
$group_entity_type = $this->resolveEntityTypeFromTable((string) $this->definition['base']) ?? NULL;
}
$derived = $this->buildEntityToGroupUnion($group_content_entity_type, $group_entity_type);
if (!$derived) {
// If there is no mapping, do nothing — relationship can't apply.
return;
}
// Ensure our base table is available and get its alias.
$this->ensureMyTable();
// Determine base entity (group content) identifiers.
$base_entity_type = $this->view->storage->get('base_entity_type') ?: $group_content_entity_type;
$base_definition = $this->entityTypeManager->getDefinition($base_entity_type);
$base_id_key = $base_definition->getKey('id');
// Determine group entity base table and id field.
$group_definition = $this->entityTypeManager->getDefinition($group_entity_type);
$group_id_key = $group_definition->getKey('id');
$group_base_table = $group_definition->getDataTable() ?? $group_definition->getBaseTable();
// Join mapping (entity_id, group_id) as a table formula to the base.
$join_map = $this->joinManager->createInstance('standard', [
'table' => 'og_group_content_to_group_map',
'field' => 'entity_id',
'left_table' => $this->tableAlias,
'left_field' => $base_id_key,
'type' => !empty($this->options['required']) ? 'INNER' : 'LEFT',
'adjusted' => TRUE,
'table formula' => $derived,
]);
$map_alias = $this->query->addRelationship('og_group_content_to_group_map', $join_map, 'og_group_content_to_group_map', $this->relationship);
// Join the group base table to the mapping on group_id and expose
// the group table alias as the relationship alias.
$join_group = $this->joinManager->createInstance('standard', [
'table' => $group_base_table,
'field' => $group_id_key,
'left_table' => $map_alias,
'left_field' => 'group_id',
'type' => !empty($this->options['required']) ? 'INNER' : 'LEFT',
'adjusted' => TRUE,
]);
$this->alias = $this->query->addRelationship('og_group_from_group_content', $join_group, $group_base_table, $map_alias);
}
/**
* Resolve an entity type ID from a given base or data table name.
*
* @internal
*/
private function resolveEntityTypeFromTable(string $table): ?string {
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $definition) {
$base = $definition->getBaseTable();
$data = $definition->getDataTable();
if ($table === $base || ($data && $table === $data)) {
return (string) $entity_type_id;
}
}
return NULL;
}
/**
* Builds a UNION SELECT mapping group content entities to their groups.
*
* Iterates over all group audience fields for the given group content entity
* type and group entity type, creating a UNION query selecting
* (entity_id, group_id) pairs. This allows relating group content to its OG
* groups across any audience field.
*/
protected function buildEntityToGroupUnion(string $group_content_entity_type, string $group_entity_type): ?SelectInterface {
try {
$group_content_bundles = $this->groupTypeManager
->getAllGroupContentBundlesByEntityType($group_content_entity_type);
}
catch (\InvalidArgumentException $e) {
return NULL;
}
if (empty($group_content_bundles)) {
return NULL;
}
$union = NULL;
$seen_field_names = [];
foreach ($group_content_bundles as $bundle_id) {
// Ask OG which audience fields exist for this bundle and group type.
$field_definitions = $this->groupAudienceHelper->getAllGroupAudienceFields(
$group_content_entity_type,
$bundle_id,
$group_entity_type
);
foreach ($field_definitions as $field_definition) {
$field_name = $field_definition
->getFieldStorageDefinition()
->getName();
// Avoid adding duplicate subqueries if multiple bundles share a field.
if (isset($seen_field_names[$field_name])) {
continue;
}
$seen_field_names[$field_name] = TRUE;
$select = $this->database
->select($group_content_entity_type . '__' . $field_name, 't')
->distinct()
->fields('t', ['entity_id']);
$select->addExpression("t.{$field_name}_target_id", 'group_id');
$select->condition('t.deleted', 0);
if ($union instanceof SelectInterface) {
$union->union($select);
}
else {
$union = $select;
}
}
}
return $union ?? NULL;
}
}
