From 255448529b03dfb0ee024810cd5964b3e92769e5 Mon Sep 17 00:00:00 2001
From: Yousef Bajawi <jose@josebcdev.com>
Date: Mon, 7 Nov 2016 17:57:21 +0200
Subject: [PATCH] Option to create Entityqueue tab on Entity pages

---
 entityqueue.links.task.yml                     |   3 +
 entityqueue.routing.yml                        |  18 +++
 entityqueue.services.yml                       |   6 +
 src/Controller/EntityQueueUIController.php     | 152 +++++++++++++++++++++++++
 src/Entity/EntitySubqueue.php                  |  26 ++++-
 src/Plugin/Derivative/EntityqueueLocalTask.php |  76 +++++++++++++
 src/Routing/RouteSubscriber.php                |  96 ++++++++++++++++
 7 files changed, 376 insertions(+), 1 deletion(-)
 create mode 100644 entityqueue.links.task.yml
 create mode 100644 src/Plugin/Derivative/EntityqueueLocalTask.php
 create mode 100644 src/Routing/RouteSubscriber.php

diff --git a/entityqueue.links.task.yml b/entityqueue.links.task.yml
new file mode 100644
index 0000000..5989157
--- /dev/null
+++ b/entityqueue.links.task.yml
@@ -0,0 +1,3 @@
+entityqueue.entities:
+  class: \Drupal\Core\Menu\LocalTaskDefault
+  deriver: \Drupal\entityqueue\Plugin\Derivative\EntityqueueLocalTask
diff --git a/entityqueue.routing.yml b/entityqueue.routing.yml
index a5afbd4..7f3e64d 100644
--- a/entityqueue.routing.yml
+++ b/entityqueue.routing.yml
@@ -63,3 +63,21 @@ entity.entity_subqueue.add_form:
     _title: 'Add subqueue'
   requirements:
     _entity_create_access: 'entity_subqueue:{entity_queue}'
+
+entity.entity_subqueue.add_item:
+  path: '/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}/{entity}/add-item'
+  defaults:
+    _controller: '\Drupal\entityqueue\Controller\EntityQueueUIController::subqueueAjaxOperation'
+    op: addItem
+  requirements:
+    _permission: 'administer entityqueue+manipulate entityqueues+manipulate all entityqueues'
+    _csrf_token: 'TRUE'
+
+entity.entity_subqueue.remove_item:
+  path: '/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}/{entity}/remove-item'
+  defaults:
+    _controller: '\Drupal\entityqueue\Controller\EntityQueueUIController::subqueueAjaxOperation'
+    op: removeItem
+  requirements:
+    _permission: 'administer entityqueue+manipulate entityqueues+manipulate all entityqueues'
+    _csrf_token: 'TRUE'
diff --git a/entityqueue.services.yml b/entityqueue.services.yml
index 4d88935..aceb26f 100644
--- a/entityqueue.services.yml
+++ b/entityqueue.services.yml
@@ -2,3 +2,9 @@ services:
   plugin.manager.entityqueue.handler:
     class: Drupal\entityqueue\EntityQueueHandlerManager
     arguments: ['@container.namespaces', '@cache.default', '@module_handler']
+
+  entityqueue.route_subscriber:
+    class: Drupal\entityqueue\Routing\RouteSubscriber
+    arguments: ['@entity_type.manager']
+    tags:
+      - { name: event_subscriber }
\ No newline at end of file
diff --git a/src/Controller/EntityQueueUIController.php b/src/Controller/EntityQueueUIController.php
index 58be72f..3b38a16 100644
--- a/src/Controller/EntityQueueUIController.php
+++ b/src/Controller/EntityQueueUIController.php
@@ -7,6 +7,14 @@ use Drupal\entityqueue\EntityQueueInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Ajax\RedirectCommand;
+use Drupal\entityqueue\Entity\EntityQueue;
+use Drupal\entityqueue\Entity\EntitySubqueue;
+use Drupal\entityqueue\EntitySubqueueInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Access\AccessResult;
 
 /**
  * Returns responses for Views UI routes.
@@ -30,6 +38,91 @@ class EntityQueueUIController extends ControllerBase {
   }
 
   /**
+   * Builds the entity add to subqueues page.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param string $entity_type_id
+   *   (optional) The entity type ID.
+   *
+   * @return array
+   *   Array of page elements to render.
+   */
+  public function entitySubqueueList(RouteMatchInterface $route_match, $entity_type_id = NULL) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $route_match->getParameter($entity_type_id);
+  
+    return $this->entityGetAllowedSubqueList($entity);
+  }
+
+  /**
+   * Get entity list for subqueues allowed for this entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface
+   *   The entity.
+   *
+   * @return array
+   *   Array of page elements to render.
+   */
+  public function entityGetAllowedSubqueList($entity) {
+    $subqueues = EntitySubqueue::loadMultiple();
+    $list_builder = $this->entityTypeManager()->getListBuilder('entity_queue');
+
+    $build['#title'] = $this->t('Entityqueues for %title', ['%title' => $entity->label()]);
+    $build['#type'] = 'container';
+    $build['#attributes']['id'] = 'entity-queue-list';
+    $build['#attached']['library'][] = 'core/drupal.ajax';
+    $build['table'] = array(
+      '#type' => 'table',
+      '#attributes' => array(
+        'class' => array('entity-queue-listing-table'),
+      ),
+      '#header' => $list_builder->buildHeader(),
+      '#rows' => array(),
+      '#cache' => [],
+    );
+  
+    foreach ($subqueues as $subqueue) {
+      $queue = $subqueue->getQueue();
+      $queue_settings = $queue->getEntitySettings();
+      $target_bundles = !empty($queue_settings['handler_settings']['target_bundles']) ? $queue_settings['handler_settings']['target_bundles'] : [];
+      if ($queue_settings['target_type'] == $entity->getEntityTypeId() && (empty($target_bundles) || in_array($entity->bundle(), $target_bundles))) {
+        $row = $list_builder->buildRow($queue);
+        // Check if entity is in queue
+        $subqueue_items = $subqueue->get('items')->getValue();
+        if(in_array($entity->id(), array_column($subqueue_items, 'target_id'))) {
+          $url = Url::fromRoute('entity.entity_subqueue.remove_item', ['entity_queue' => $queue->id(), 'entity_subqueue' => $subqueue->id(), 'entity' => $entity->id()]);
+          $row['data']['operations']['data']['#links'] = [
+            'remove-item' => [
+              'title' => $this->t('Remove from queue'),
+              'url' => $url,
+              'attributes' => [
+                'class' => ['use-ajax'],
+              ],
+            ],
+          ];
+        }
+        else {
+          $url = Url::fromRoute('entity.entity_subqueue.add_item', ['entity_queue' => $queue->id(), 'entity_subqueue' => $subqueue->id(), 'entity' => $entity->id()]);
+          $row['data']['operations']['data']['#links'] = [
+            'add-item' => [
+              'title' => $this->t('Add to queue'),
+              'url' => $url,
+              'attributes' => [
+                'class' => ['use-ajax'],
+              ],
+            ],
+          ];
+        }
+        $build['table']['#rows'][$queue->id()] = $row;
+      }
+    }
+    $build['table']['#empty'] = $this->t('There are no available queues.');
+  
+    return $build;
+  }
+
+  /**
    * Returns a form to add a new subqeue.
    *
    * @param \Drupal\entityqueue\EntityQueueInterface $entity_queue
@@ -73,4 +166,63 @@ class EntityQueueUIController extends ControllerBase {
     return $this->redirect('entity.entity_queue.collection');
   }
 
+  /**
+   * Calls a method on an entity subqueue page and reloads the page.
+   *
+   * @param \Drupal\entityqueue\EntitySubqueueInterface $entity_subqueue
+   *   The view being acted upon.
+   * @param string $op
+   *   The operation to perform, e.g., 'add-item' or 'remove-item'.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse|\Symfony\Component\HttpFoundation\RedirectResponse
+   *   Either returns a rebuilt listing page as an AJAX response, or redirects
+   *   back to the subqueue page.
+   */
+  public function subqueueAjaxOperation(EntitySubqueueInterface $entity_subqueue, $op, Request $request) {
+    $entity_id = $request->get('entity');
+    $entity = \Drupal::entityTypeManager()->getStorage($entity_subqueue->getQueue()->getTargetEntityTypeId())->load($entity_id);
+    // Perform the operation.
+    $entity_subqueue->$op($entity_id)->save();
+    // If the request is via AJAX, return the rendered list as JSON.
+    if ($request->request->get('js')) {
+      $list = $this->entityGetAllowedSubqueList($entity);
+      $response = new AjaxResponse();
+      $response->addCommand(new ReplaceCommand('#entity-queue-list', $list));
+      return $response;
+    }
+    else {
+      $response = new AjaxResponse();
+      $response->addCommand(new RedirectCommand(''));
+      return $response;
+    }
+  }
+
+  /**
+   * Checks access for a specific request.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param string $entity_type_id
+   *   (optional) The entity type ID.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(RouteMatchInterface $route_match, $entity_type_id = NULL) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $route_match->getParameter($entity_type_id);
+    $subqueues = EntitySubqueue::loadMultiple();
+    foreach ($subqueues as $subqueue) {
+      $queue = $subqueue->getQueue();
+      $queue_settings = $queue->getEntitySettings();
+      $target_bundles = !empty($queue_settings['handler_settings']['target_bundles']) ? $queue_settings['handler_settings']['target_bundles'] : [];
+      if ($queue_settings['target_type'] == $entity_type_id && (empty($target_bundles) || in_array($entity->bundle(), $target_bundles))) {
+        return AccessResult::allowed();
+      }
+    }
+  
+    return AccessResult::forbidden();
+  }
 }
diff --git a/src/Entity/EntitySubqueue.php b/src/Entity/EntitySubqueue.php
index f69c4ca..4d1fcfb 100644
--- a/src/Entity/EntitySubqueue.php
+++ b/src/Entity/EntitySubqueue.php
@@ -49,7 +49,9 @@ use Drupal\user\UserInterface;
  *   links = {
  *     "canonical" = "/admin/structure/entityqueue/{entity_queue}",
  *     "edit-form" = "/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}",
- *     "delete-form" = "/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}/delete"
+ *     "delete-form" = "/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}/delete",
+ *     "add-item" = "/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}/{entity}/add-item",
+ *     "remove-item" = "/admin/structure/entityqueue/{entity_queue}/{entity_subqueue}/{entity}/remove-item"
  *   },
  *   constraints = {
  *     "QueueSize" = {}
@@ -277,6 +279,28 @@ class EntitySubqueue extends ContentEntityBase implements EntitySubqueueInterfac
   }
 
   /**
+   * Adds an entity to a subqueue
+   */
+  public function addItem($entity_id) {
+    $this->get('items')->appendItem($entity_id);
+    return $this;
+  }
+
+  /**
+   * Removes an entity from a subqueue
+   */
+  public function removeItem($entity_id) {
+    $subqueue_items = $this->get('items')->getValue();
+    foreach ($subqueue_items as $key => $item) {
+      if ($item['target_id'] == $entity_id) {
+        unset($subqueue_items[$key]);
+      }
+    }
+    $this->get('items')->setValue($subqueue_items);
+    return $this;
+  }
+
+  /**
    * Default value callback for 'uid' base field definition.
    *
    * @see ::baseFieldDefinitions()
diff --git a/src/Plugin/Derivative/EntityqueueLocalTask.php b/src/Plugin/Derivative/EntityqueueLocalTask.php
new file mode 100644
index 0000000..467c613
--- /dev/null
+++ b/src/Plugin/Derivative/EntityqueueLocalTask.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entityqueue\Plugin\Derivative\EntityqueueLocalTask.
+ */
+
+namespace Drupal\entityqueue\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides local task definitions for all entity bundles.
+ */
+class EntityqueueLocalTask extends DeriverBase implements ContainerDeriverInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity manager
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Creates an EntityqueueLocalTask object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The translation manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
+    $this->entityManager = $entity_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    $this->derivatives = array();
+
+    foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
+      if ($entity_type->hasViewBuilderClass() && $entity_type->hasLinkTemplate('canonical')) {
+        $entityqueue_route_name = "entity.$entity_type_id.entityqueue";
+        $this->derivatives[$entityqueue_route_name] = array(
+          'entity_type' => $entity_type_id,
+          'title' => $this->t('Entityqueue'),
+          'route_name' => $entityqueue_route_name,
+          'base_route' => "entity.$entity_type_id.canonical",
+          'weight' => 21, // move after edit, delete, revisions ... etc tabs.
+        ) + $base_plugin_definition;
+      }
+    }
+
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+}
diff --git a/src/Routing/RouteSubscriber.php b/src/Routing/RouteSubscriber.php
new file mode 100644
index 0000000..a025d5a
--- /dev/null
+++ b/src/Routing/RouteSubscriber.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\entityqueue\Routing;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\RouteSubscriberBase;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Subscriber for entityqueue routes.
+ */
+class RouteSubscriber extends RouteSubscriberBase {
+
+  /**
+   * The entity type manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new RouteSubscriber object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_manager) {
+    $this->entityTypeManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterRoutes(RouteCollection $collection) {
+    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
+      // Try to get the route from the current collection.
+      $link_template = $entity_type->getLinkTemplate('canonical');
+      if (strpos($link_template, '/') !== FALSE) {
+        $base_path = '/' . $link_template;
+      }
+      else {
+        if (!$entity_route = $collection->get("entity.$entity_type_id.canonical")) {
+          continue;
+        }
+        $base_path = $entity_route->getPath();
+      }
+
+      // Inherit admin route status from edit route, if exists.
+      $is_admin = FALSE;
+      $route_name = "entity.$entity_type_id.edit_form";
+      if ($edit_route = $collection->get($route_name)) {
+        $is_admin = (bool) $edit_route->getOption('_admin_route');
+      }
+
+      $path = $base_path . '/entityqueue';
+
+      $route = new Route(
+        $path,
+        array(
+          '_controller' => '\Drupal\entityqueue\Controller\EntityQueueUIController::entitySubqueueList',
+          'entity_type_id' => $entity_type_id,
+          '_title' => 'Entityqueues',
+        ),
+        array(
+          '_permission' => 'administer entityqueue+manipulate entityqueues+manipulate all entityqueues',
+          '_custom_access' => 'Drupal\entityqueue\Controller\EntityQueueUIController::access',
+        ),
+        array(
+          'parameters' => array(
+            $entity_type_id => array(
+              'type' => 'entity:' . $entity_type_id,
+            ),
+          ),
+          '_admin_route' => $is_admin,
+        )
+      );
+      $route_name = "entity.$entity_type_id.entityqueue";
+      $collection->add($route_name, $route);
+
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events = parent::getSubscribedEvents();
+    // Should run after AdminRouteSubscriber so the routes can inherit admin
+    // status of the edit routes on entities. Therefore priority -210.
+    $events[RoutingEvents::ALTER] = array('onAlterRoutes', -210);
+    return $events;
+  }
+
+}
-- 
1.9.1

