From 1a3a3dda1141758c240d0eba0963e3a75c02f207 Mon Sep 17 00:00:00 2001
From: Kristiaan Van den Eynde <magentix@gmail.com>
Date: Thu, 19 May 2016 19:55:14 +0200
Subject: [PATCH] Issue #2706495 by joachim, kristiaanvandeneynde: add Views
 reverse relationships from entities to Group Content

---
 src/Entity/Views/GroupContentViewsData.php         |  76 ++++++++
 .../relationship/GroupContentToEntityReverse.php   | 191 +++++++++++++++++++++
 2 files changed, 267 insertions(+)
 create mode 100644 src/Plugin/views/relationship/GroupContentToEntityReverse.php

diff --git a/src/Entity/Views/GroupContentViewsData.php b/src/Entity/Views/GroupContentViewsData.php
index 5c8faef..0aac908 100644
--- a/src/Entity/Views/GroupContentViewsData.php
+++ b/src/Entity/Views/GroupContentViewsData.php
@@ -7,19 +7,95 @@
 
 namespace Drupal\group\Entity\Views;
 
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
 use Drupal\views\EntityViewsData;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Provides the views data for the group content entity type.
+ *
+ * @todo replace the GroupContent -> Entity relationship with one of our own.
  */
 class GroupContentViewsData extends EntityViewsData {
 
   /**
+   * The group content enabler plugin manager.
+   *
+   * @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface
+   */
+  protected $pluginManager;
+
+  /**
+   * Constructs a GroupContentViewsData object.
+   *
+   * @param \Drupal\group\Plugin\GroupContentEnablerManagerInterface $plugin_manager
+   *   The group content enabler plugin manager.
+   */
+  function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager, GroupContentEnablerManagerInterface $plugin_manager) {
+    parent::__construct($entity_type, $storage_controller, $entity_manager, $module_handler, $translation_manager);
+    $this->pluginManager = $plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('entity.manager')->getStorage($entity_type->id()),
+      $container->get('entity.manager'),
+      $container->get('module_handler'),
+      $container->get('string_translation'),
+      $container->get('plugin.manager.group_content_enabler')
+    );
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getViewsData() {
     $data = parent::getViewsData();
 
+    // Get the data table for GroupContent entities.
+    $data_table = $this->entityType->getDataTable();
+
+    // Relate entities to group content.
+    $entity_types = $this->entityManager->getDefinitions();
+
+    $group_content_plugins = $this->pluginManager->getAll();
+    foreach ($group_content_plugins as $plugin) {
+      $entity_type_id = $plugin->getEntityTypeId();
+      $entity_type = $entity_types[$entity_type_id];
+      $entity_data_table = $entity_type->getDataTable() ?: $entity_type->getBaseTable();
+
+      // We only add one 'group_content' reverse relationship per entity type.
+      if (isset($data[$entity_data_table]['group_content'])) {
+        continue;
+      }
+
+      $t_args = [
+        '@entity_type' => $entity_type->getLabel(),
+      ];
+
+      $data[$entity_data_table]['group_content'] = array(
+        'title' => t('@entity_type group content', $t_args),
+        'help' => t('Relate @entity_type to the entity that represents its relationship to a Group.', $t_args),
+        'relationship' => array(
+          'group' => $this->t('Group content'),
+          'base' => $data_table,
+          'base field' => 'entity_id',
+          'relationship field' => $entity_type->getKey('id'),
+          'id' => 'group_content_to_entity_reverse',
+          'label' => t('@entity_type group content', $t_args),
+        ),
+      );
+    }
+
     return $data;
   }
 
diff --git a/src/Plugin/views/relationship/GroupContentToEntityReverse.php b/src/Plugin/views/relationship/GroupContentToEntityReverse.php
new file mode 100644
index 0000000..eaa2d63
--- /dev/null
+++ b/src/Plugin/views/relationship/GroupContentToEntityReverse.php
@@ -0,0 +1,191 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\group\Plugin\views\relationship\GroupContentToEntityReverse.
+ */
+
+namespace Drupal\group\Plugin\views\relationship;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\group\Entity\GroupContentType;
+use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
+use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
+use Drupal\views\Plugin\ViewsHandlerManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * A relationship handlers which reverses group content entity references.
+ *
+ * @ingroup views_relationship_handlers
+ *
+ * @ViewsRelationship("group_content_to_entity_reverse")
+ */
+class GroupContentToEntityReverse extends RelationshipPluginBase {
+
+  /**
+   * The Views join plugin manager.
+   *
+   * @var \Drupal\views\Plugin\ViewsHandlerManager
+   */
+  protected $joinManager;
+  
+  /**
+   * The group content enabler plugin manager.
+   *
+   * @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface
+   */
+  protected $pluginManager;
+
+  /**
+   * Constructs an GroupContentToEntityReverse object.
+   *
+   * @param \Drupal\views\Plugin\ViewsHandlerManager $join_manager
+   *   The views plugin join manager.
+   * @param \Drupal\group\Plugin\GroupContentEnablerManagerInterface $plugin_manager
+   *   The group content enabler plugin manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, ViewsHandlerManager $join_manager, GroupContentEnablerManagerInterface $plugin_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->joinManager = $join_manager;
+    $this->pluginManager = $plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.views.join'),
+      $container->get('plugin.manager.group_content_enabler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function defineOptions() {
+    $options = parent::defineOptions();
+
+    // Set the defaults for the checkboxes which allow you to filter by plugin.
+    $options['group_content_plugins']['default'] = [];
+
+    // Set default values that mimic those submitted by checkboxes to set which
+    // group content types we're actually filtering on. This defaults to all of
+    // the ones that could be used by the entity type set on the handler.
+    $group_content_type_ids = array_keys(GroupContentType::loadByEntityTypeId($this->definition['entity_type']));
+    $options['group_content_types']['default'] = array_combine($group_content_type_ids, $group_content_type_ids);
+
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+    parent::buildOptionsForm($form, $form_state);
+    $options = [];
+
+    // Retrieve all of the plugins that can handle this entity type.
+    /** @var \Drupal\group\Plugin\GroupContentEnablerInterface $plugin */
+    foreach ($this->pluginManager->getAll() as $plugin_id => $plugin) {
+      if ($plugin->getEntityTypeId() === $this->definition['entity_type']) {
+        $options[$plugin_id] = $plugin->getLabel();
+      }
+    }
+
+    if (count($options) > 1) {
+      $form['group_content_plugins'] = [
+        '#type' => 'checkboxes',
+        '#title' => $this->t('Filter by plugin'),
+        '#description' => $this->t('Refine the result by plugin. Leave empty to fetch all results.'),
+        '#options' => $options,
+        '#weight' => -2,
+        '#default_value' => $this->options['group_content_plugins'],
+      ];
+    }
+  }
+
+  /**
+   * We save the group content types this handler will filter on based off of
+   * which plugins were selected in the options form. This will allow us to
+   * easily filter the query on group content types without having to run an
+   * extra query or add a complicated join to the view query.
+   *
+   * {@inheritdoc}
+   */
+  public function validateOptionsForm(&$form, FormStateInterface $form_state) {
+    // Retrieve the selected group content plugins.
+    $options = $form_state->getValue('options');
+    $plugin_ids = array_filter($options['group_content_plugins']);
+
+    // If no plugin was selected, we use all of the possible group content types
+    // for the entity type this handler was selected for.
+    $group_content_types = empty($plugin_ids)
+      ? GroupContentType::loadByEntityTypeId($this->definition['entity_type'])
+      : GroupContentType::loadByContentPluginId($plugin_ids);
+
+    // Retrieve the "selected" group content type IDs as valid options.
+    $group_content_type_ids = array_keys($group_content_types);
+    $selected = array_combine($group_content_type_ids, $group_content_type_ids);
+
+    // Create an array containing all options as unselected so we can add it to
+    // the array containing the selected options. This will result in an array
+    // which only has the actually selected options with non-zero values.
+    $unselected = array_fill_keys($this->options['group_content_types'], 0);
+
+    // Save the options as if they were coming from a checkboxes element.
+    $options['group_content_types'] = $selected + $unselected;
+    $form_state->setValue('options', $options);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $this->ensureMyTable();
+
+    // Build the join definition.
+    $def = $this->definition;
+    $def['table'] = $this->definition['base'];
+    $def['field'] = $this->definition['base field'];
+    $def['left_table'] = $this->tableAlias;
+    $def['left_field'] = $this->realField;
+    $def['adjusted'] = TRUE;
+
+    // Change the join to INNER if the relationship is required.
+    if (!empty($this->options['required'])) {
+      $def['type'] = 'INNER';
+    }
+
+    // If there were extra join conditions added in the definition, use them.
+    if (!empty($this->definition['extra'])) {
+      $def['extra'] = $this->definition['extra'];
+    }
+
+    // Then add our own join condition, namely the group content type IDs.
+    $def['extra'][] = [
+      'field' => 'type',
+      'value' => array_filter($this->options['group_content_types']),
+    ];
+
+    // Use the standard join plugin unless instructed otherwise.
+    $join_id = !empty($def['join_id']) ? $def['join_id'] : 'standard';
+    $join = $this->joinManager->createInstance($join_id, $def);
+
+    // Add the join using a more verbose alias.
+    $alias = $def['table'] . '_' . $this->table;
+    $this->alias = $this->query->addRelationship($alias, $join, $this->definition['base'], $this->relationship);
+
+    // Add access tags if the base table provides it.
+    $table_data = $this->viewsData->get($def['table']);
+    if (empty($this->query->options['disable_sql_rewrite']) && isset($table_data['table']['base']['access query tag'])) {
+      $access_tag = $table_data['table']['base']['access query tag'];
+      $this->query->addTag($access_tag);
+    }
+  }
+
+}
-- 
2.4.9 (Apple Git-60)

