From b0ef6a4919bec6b2202ba30940ab2bf6b996bd67 Mon Sep 17 00:00:00 2001
From: Camille Liccia <camille.liccia@smile.fr>
Date: Fri, 16 Nov 2018 15:46:32 +0100
Subject: [PATCH] Issue #741333: Add Block_content entity support for group.

---
 .../gblock_content.group.permissions.yml           |   3 +
 modules/gblock_content/gblock_content.info.yml     |   9 ++
 .../gblock_content/gblock_content.links.action.yml |   5 +
 modules/gblock_content/gblock_content.module       |  81 +++++++++++++++
 modules/gblock_content/gblock_content.routing.yml  |   2 +
 modules/gblock_content/gblock_content.services.yml |   5 +
 .../Access/GroupBlockContentAdminAccessCheck.php   |  52 ++++++++++
 .../src/Controller/GroupBlockContentController.php | 114 +++++++++++++++++++++
 .../GroupConfigEnabler/GroupBlockContentType.php   |  18 ++++
 .../GroupContentEnabler/GroupBlockContent.php      | 108 +++++++++++++++++++
 .../GroupBlockContentDeriver.php                   |  27 +++++
 .../src/Routing/GroupBlockContentRouteProvider.php |  57 +++++++++++
 12 files changed, 481 insertions(+)
 create mode 100644 modules/gblock_content/gblock_content.group.permissions.yml
 create mode 100644 modules/gblock_content/gblock_content.info.yml
 create mode 100644 modules/gblock_content/gblock_content.links.action.yml
 create mode 100644 modules/gblock_content/gblock_content.module
 create mode 100644 modules/gblock_content/gblock_content.routing.yml
 create mode 100644 modules/gblock_content/gblock_content.services.yml
 create mode 100644 modules/gblock_content/src/Access/GroupBlockContentAdminAccessCheck.php
 create mode 100644 modules/gblock_content/src/Controller/GroupBlockContentController.php
 create mode 100644 modules/gblock_content/src/Plugin/GroupConfigEnabler/GroupBlockContentType.php
 create mode 100644 modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContent.php
 create mode 100644 modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContentDeriver.php
 create mode 100644 modules/gblock_content/src/Routing/GroupBlockContentRouteProvider.php

diff --git a/modules/gblock_content/gblock_content.group.permissions.yml b/modules/gblock_content/gblock_content.group.permissions.yml
new file mode 100644
index 0000000..b03fa18
--- /dev/null
+++ b/modules/gblock_content/gblock_content.group.permissions.yml
@@ -0,0 +1,3 @@
+access group_block_content overview:
+  title: 'Access group block content overview'
+  description: 'Access the overview of all group block content items, regardless of bundle'
diff --git a/modules/gblock_content/gblock_content.info.yml b/modules/gblock_content/gblock_content.info.yml
new file mode 100644
index 0000000..fca5256
--- /dev/null
+++ b/modules/gblock_content/gblock_content.info.yml
@@ -0,0 +1,9 @@
+name: 'Group Block content'
+description: 'Enables Group functionality for block content items'
+package: 'Group'
+type: 'module'
+version: 1.0
+core_version_requirement: ^8 || ^9
+dependencies:
+  - 'block_content'
+  - 'group'
diff --git a/modules/gblock_content/gblock_content.links.action.yml b/modules/gblock_content/gblock_content.links.action.yml
new file mode 100644
index 0000000..560512f
--- /dev/null
+++ b/modules/gblock_content/gblock_content.links.action.yml
@@ -0,0 +1,5 @@
+entity.block_content_item.group_add_form:
+  route_name: entity.block_content_item.group_add_form
+  title: 'Add Block content item to group'
+  appears_on:
+    - entity.block_content_item.group_overview_form
diff --git a/modules/gblock_content/gblock_content.module b/modules/gblock_content/gblock_content.module
new file mode 100644
index 0000000..ad6a0a9
--- /dev/null
+++ b/modules/gblock_content/gblock_content.module
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Enables Group functionality for the Block content module.
+ */
+
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\group\Entity\GroupContentType;
+
+/**
+ * Implements hook_node_access().
+ *
+ * When trying to view, update or delete a node it suffices to have the right to
+ * do so in only one group the node belongs to. If you wish to prevent any such
+ * action on your own terms, implement hook_node_access() in your module.
+ */
+function gblock_content_block_content_access(EntityInterface $block_content, $op, AccountInterface $account) {
+  // We do not care about create access as we have our own wizard for that. Any
+  // operation aside from 'view', 'update' and 'delete' is also unsupported.
+  if (!in_array($op, ['view', 'update', 'delete'])) {
+    return AccessResult::neutral();
+  }
+
+  $plugin_id = 'group_block_content:' . $block_content->bundle();
+
+  // Only act if there are group content types for this node type.
+  $group_content_types = GroupContentType::loadByContentPluginId($plugin_id);
+  if (empty($group_content_types)) {
+    return AccessResult::neutral();
+  }
+
+  // Load all the group content for this node.
+  $group_contents = \Drupal::entityTypeManager()
+    ->getStorage('group_content')
+    ->loadByProperties([
+      'type' => array_keys($group_content_types),
+      'entity_id' => $block_content->id(),
+    ]);
+
+  // If the block content item does not belong to any group, we have nothing to say.
+  if (empty($group_contents)) {
+    return AccessResult::neutral();
+  }
+
+  /** @var \Drupal\group\Entity\GroupInterface[] $groups */
+  $groups = [];
+  foreach ($group_contents as $group_content) {
+    /** @var \Drupal\group\Entity\GroupContentInterface $group_content */
+    $group = $group_content->getGroup();
+    $groups[$group->id()] = $group;
+  }
+
+  // From this point on you need group to allow you to perform the requested
+  // operation. If you are not granted access for a block content item belonging to a group,
+  // you should be denied access instead.
+  switch ($op) {
+    case 'view':
+      foreach ($groups as $group) {
+        if ($group->hasPermission("view " . $block_content->bundle() . " item", $account) ||
+          $group->hasPermission("view " . $plugin_id . " entity", $account)) {
+          return AccessResult::allowed();
+        }
+      }
+      break;
+
+    case 'update':
+    case 'delete':
+      foreach ($groups as $group) {
+        if ($group->hasPermission("edit " . $block_content->bundle() . " item", $account) ||
+          $group->hasPermission("update any " . $plugin_id . " entity", $account)) {
+          return AccessResult::allowed();
+        }
+      }
+      break;
+  }
+
+  return AccessResult::forbidden();
+}
diff --git a/modules/gblock_content/gblock_content.routing.yml b/modules/gblock_content/gblock_content.routing.yml
new file mode 100644
index 0000000..9a06152
--- /dev/null
+++ b/modules/gblock_content/gblock_content.routing.yml
@@ -0,0 +1,2 @@
+route_callbacks:
+  - '\Drupal\gblock_content\Routing\GroupBlockContentRouteProvider::getRoutes'
diff --git a/modules/gblock_content/gblock_content.services.yml b/modules/gblock_content/gblock_content.services.yml
new file mode 100644
index 0000000..9132643
--- /dev/null
+++ b/modules/gblock_content/gblock_content.services.yml
@@ -0,0 +1,5 @@
+services:
+  access_check.gblock_content.admin:
+    class: Drupal\gblock_content\Access\GroupBlockContentAdminAccessCheck
+    tags:
+      - { name: access_check, applies_to: _group_block_content_admin_access }
diff --git a/modules/gblock_content/src/Access/GroupBlockContentAdminAccessCheck.php b/modules/gblock_content/src/Access/GroupBlockContentAdminAccessCheck.php
new file mode 100644
index 0000000..6093eb6
--- /dev/null
+++ b/modules/gblock_content/src/Access/GroupBlockContentAdminAccessCheck.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\gblock_content\Access;
+
+use Drupal\block_content\BlockContentInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\group\Entity\GroupInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access to group block content forms.
+ */
+class GroupBlockContentAdminAccessCheck implements AccessInterface {
+
+  /**
+   * Checks access to the group block content pages.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   * @param \Drupal\group\Entity\GroupInterface $group
+   *   The group with which to relate the item.
+   * @param \Drupal\block_content\BlockContentInterface $block_content_type
+   *   The block content type in which to create the item.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, AccountInterface $account, GroupInterface $group, BlockContentInterface $block_content_type) {
+    $needs_access = $route->getRequirement('_group_block_content_admin_access') === 'TRUE';
+
+    // We can only get the group content type ID if the plugin is installed.
+    if (!$group->getGroupType()->hasContentPlugin("group_block_content:$block_content_type")) {
+      return AccessResult::neutral();
+    }
+
+    // Determine whether the user can manipulate items of the provided type.
+    $access = FALSE;
+    foreach (['create', 'edit', 'delete'] as $op) {
+      $permission = "$op $block_content_type item";
+      $access = $access || $group->hasPermission($permission, $account);
+    }
+
+    // Only allow access if the user can manipulate group items of the provided
+    // type or if he doesn't need access to do so.
+    return AccessResult::allowedIf($access xor !$needs_access);
+  }
+
+}
diff --git a/modules/gblock_content/src/Controller/GroupBlockContentController.php b/modules/gblock_content/src/Controller/GroupBlockContentController.php
new file mode 100644
index 0000000..886ab1b
--- /dev/null
+++ b/modules/gblock_content/src/Controller/GroupBlockContentController.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\gblock_content\Controller;
+
+use Drupal\Core\Entity\EntityFormBuilderInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\group\Entity\Controller\GroupContentController;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\group\Plugin\GroupContentEnablerManagerInterface;
+use Drupal\user\PrivateTempStoreFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for 'group_block_content' GroupContent routes.
+ */
+class GroupBlockContentController extends GroupContentController {
+
+  /**
+   * The group content plugin manager.
+   *
+   * @var \Drupal\group\Plugin\GroupContentEnablerManagerInterface
+   */
+  protected $pluginManager;
+
+  /**
+   * Constructs a new GroupBlockContentController.
+   *
+   * @param \Drupal\group\Plugin\GroupContentEnablerManagerInterface $plugin_manager
+   *   The group content plugin manager.
+   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
+   *   The private store factory.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityFormBuilderInterface $entity_form_builder
+   *   The entity form builder.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(GroupContentEnablerManagerInterface $plugin_manager, PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, EntityFormBuilderInterface $entity_form_builder, RendererInterface $renderer) {
+    parent::__construct($temp_store_factory, $entity_type_manager, $entity_form_builder, $renderer);
+    $this->pluginManager = $plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.group_content_enabler'),
+      $container->get('user.private_tempstore'),
+      $container->get('entity_type.manager'),
+      $container->get('entity.form_builder'),
+      $container->get('renderer')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addPage(GroupInterface $group, $create_mode = FALSE) {
+    $build = parent::addPage($group, $create_mode);
+
+    // Do not interfere with redirects.
+    if (!is_array($build)) {
+      return $build;
+    }
+
+    // Overwrite the label and description for all of the displayed bundles.
+    $storage_handler = $this->entityTypeManager->getStorage('block_content_type');
+    foreach ($this->addPageBundles($group, $create_mode) as $plugin_id => $bundle_name) {
+      if (!empty($build['#bundles'][$bundle_name])) {
+        $plugin = $group->getGroupType()->getContentPlugin($plugin_id);
+        $bundle_label = $storage_handler->load($plugin->getEntityBundle())->label();
+
+        $t_args = ['%block_content_type' => $bundle_label];
+        $description = $create_mode
+          ? $this->t('Create a block content of type %block_content_type in the group.', $t_args)
+          : $this->t('Add an existing block content of type %block_content_type to the group.', $t_args);
+
+        $build['#bundles'][$bundle_name]['label'] = $bundle_label;
+        $build['#bundles'][$bundle_name]['description'] = $description;
+      }
+    }
+
+    return $build;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function addPageBundles(GroupInterface $group, $create_mode) {
+    $bundles = [];
+
+    // Retrieve all group_block_content plugins for the group's type.
+    $plugin_ids = $this->pluginManager->getInstalledIds($group->getGroupType());
+    foreach ($plugin_ids as $key => $plugin_id) {
+      if (strpos($plugin_id, 'group_block_content:') !== 0) {
+        unset($plugin_ids[$key]);
+      }
+    }
+
+    // Retrieve all of the responsible group content types, keyed by plugin ID.
+    $storage = $this->entityTypeManager->getStorage('group_content_type');
+    $properties = ['group_type' => $group->bundle(), 'content_plugin' => $plugin_ids];
+    foreach ($storage->loadByProperties($properties) as $bundle => $group_content_type) {
+      /** @var \Drupal\group\Entity\GroupContentTypeInterface $group_content_type */
+      $bundles[$group_content_type->getContentPluginId()] = $bundle;
+    }
+
+    return $bundles;
+  }
+
+}
diff --git a/modules/gblock_content/src/Plugin/GroupConfigEnabler/GroupBlockContentType.php b/modules/gblock_content/src/Plugin/GroupConfigEnabler/GroupBlockContentType.php
new file mode 100644
index 0000000..e18d06f
--- /dev/null
+++ b/modules/gblock_content/src/Plugin/GroupConfigEnabler/GroupBlockContentType.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Drupal\gblock_content\Plugin\GroupConfigEnabler;
+
+use Drupal\group\Plugin\GroupConfigEnablerBase;
+
+/**
+ * Provides a content enabler for nodes.
+ *
+ * @GroupConfigEnabler(
+ *   id = "group_block_content_type",
+ *   label = @Translation("Group block content type"),
+ *   description = @Translation("Adds block content type to groups both publicly and privately."),
+ *   entity_type_id = "block_content_type",
+ * )
+ */
+class GroupBlockContentType extends GroupConfigEnablerBase {
+}
diff --git a/modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContent.php b/modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContent.php
new file mode 100644
index 0000000..a63c0d6
--- /dev/null
+++ b/modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContent.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\gblock_content\Plugin\GroupContentEnabler;
+
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\group\Entity\GroupInterface;
+use Drupal\group\Plugin\GroupContentEnablerBase;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a content enabler for block content.
+ *
+ * @GroupContentEnabler(
+ *   id = "group_block_content",
+ *   label = @Translation("Group block content"),
+ *   description = @Translation("Adds block content to groups both publicly and privately."),
+ *   entity_type_id = "block_content",
+ *   entity_access = TRUE,
+ *   reference_label = @Translation("Title"),
+ *   reference_description = @Translation("The title of the block content to add to the group"),
+ *   deriver = "Drupal\gblock_content\Plugin\GroupContentEnabler\GroupBlockContentDeriver"
+ * )
+ */
+class GroupBlockContent extends GroupContentEnablerBase {
+
+  /**
+   * Retrieves the block content type this plugin supports.
+   *
+   * @return \Drupal\block_content\Entity\BlockContentType
+   *   The block content type this plugin supports.
+   */
+  protected function getBlockContentType() {
+    return BlockContentType::load($this->getEntityBundle());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getGroupOperations(GroupInterface $group) {
+    $account = \Drupal::currentUser();
+    $plugin_id = $this->getPluginId();
+    $type = $this->getEntityBundle();
+    $operations = [];
+
+    if ($group->hasPermission("create $plugin_id entity", $account)) {
+      $route_params = ['group' => $group->id(), 'plugin_id' => $plugin_id];
+      $operations["gblock_content-create-$type"] = [
+        'title' => $this->t('Create @type', ['@type' => $this->getBlockContentType()->label()]),
+        'url' => new Url('entity.group_content.create_form', $route_params),
+        'weight' => 30,
+      ];
+    }
+
+    return $operations;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTargetEntityPermissions() {
+    $permissions = parent::getTargetEntityPermissions();
+    $plugin_id = $this->getPluginId();
+
+    // Add a 'view unpublished' permission by re-using most of the 'view' one.
+    $original = $permissions["view $plugin_id entity"];
+    $permissions["view unpublished $plugin_id entity"] = [
+      'title' => str_replace('View ', 'View unpublished ', $original['title']),
+    ] + $original;
+
+    return $permissions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    $config = parent::defaultConfiguration();
+    $config['entity_cardinality'] = 1;
+    return $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+
+    // Disable the entity cardinality field as the functionality of this module
+    // relies on a cardinality of 1. We don't just hide it, though, to keep a UI
+    // that's consistent with other content enabler plugins.
+    $info = $this->t("This field has been disabled by the plugin to guarantee the functionality that's expected of it.");
+    $form['entity_cardinality']['#disabled'] = TRUE;
+    $form['entity_cardinality']['#description'] .= '<br /><em>' . $info . '</em>';
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    $dependencies = parent::calculateDependencies();
+    $dependencies['config'][] = 'block_content.type.' . $this->getEntityBundle();
+    return $dependencies;
+  }
+
+}
diff --git a/modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContentDeriver.php b/modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContentDeriver.php
new file mode 100644
index 0000000..ca30185
--- /dev/null
+++ b/modules/gblock_content/src/Plugin/GroupContentEnabler/GroupBlockContentDeriver.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\gblock_content\Plugin\GroupContentEnabler;
+
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+
+class GroupBlockContentDeriver extends DeriverBase {
+
+  /**
+   * {@inheritdoc}.
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    foreach (BlockContentType::loadMultiple() as $name => $block_content_type) {
+      $label = $block_content_type->label();
+
+      $this->derivatives[$name] = [
+        'entity_bundle' => $name,
+        'label' => t('Group block content (@type)', ['@type' => $label]),
+        'description' => t('Adds %type content to groups both publicly and privately.', ['%type' => $label]),
+      ] + $base_plugin_definition;
+    }
+
+    return $this->derivatives;
+  }
+
+}
diff --git a/modules/gblock_content/src/Routing/GroupBlockContentRouteProvider.php b/modules/gblock_content/src/Routing/GroupBlockContentRouteProvider.php
new file mode 100644
index 0000000..61a1f43
--- /dev/null
+++ b/modules/gblock_content/src/Routing/GroupBlockContentRouteProvider.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\gblock_content\Routing;
+
+use Drupal\block_content\Entity\BlockContentType;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides routes for group_block_content group content.
+ */
+class GroupBlockContentRouteProvider {
+
+  /**
+   * Provides the shared collection route for group block_content plugins.
+   */
+  public function getRoutes() {
+    $routes = $plugin_ids = $permissions_add = $permissions_create = [];
+
+    foreach (BlockContentType::loadMultiple() as $name => $block_content_type) {
+      $plugin_id = "group_block_content:$name";
+
+      $plugin_ids[] = $plugin_id;
+      $permissions_add[] = "create $plugin_id content";
+      $permissions_create[] = "create $plugin_id entity";
+    }
+
+    // If there are no block_content types yet, we cannot have any plugin IDs and should
+    // therefore exit early because we cannot have any routes for them either.
+    if (empty($plugin_ids)) {
+      return $routes;
+    }
+
+    $routes['entity.group_content.group_block_content_relate_page'] = new Route('group/{group}/block_content/add');
+    $routes['entity.group_content.group_block_content_relate_page']
+      ->setDefaults([
+        '_title' => 'Relate block content',
+        '_controller' => '\Drupal\gblock_content\Controller\GroupBlockContentController::addPage',
+      ])
+      ->setRequirement('_group_permission', implode('+', $permissions_add))
+      ->setRequirement('_group_installed_content', implode('+', $plugin_ids))
+      ->setOption('_group_operation_route', TRUE);
+
+    $routes['entity.group_content.group_block_content_add_page'] = new Route('group/{group}/block_content/create');
+    $routes['entity.group_content.group_block_content_add_page']
+      ->setDefaults([
+        '_title' => 'Create block content',
+        '_controller' => '\Drupal\gblock_content\Controller\GroupBlockContentController::addPage',
+        'create_mode' => TRUE,
+      ])
+      ->setRequirement('_group_permission', implode('+', $permissions_create))
+      ->setRequirement('_group_installed_content', implode('+', $plugin_ids))
+      ->setOption('_group_operation_route', TRUE);
+
+    return $routes;
+  }
+
+}
--
2.7.4

