From ef63cffdf3dc2a873c0e1406f7b1a4a05e0c73aa Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Fri, 29 Mar 2013 09:54:05 -0700
Subject: [PATCH] Issue #1801356 by dawehner, disasm, amateescu, effulgentsia:
 Entity reference autocomplete using routes.

---
 .../entity_reference/entity_reference.module  | 156 ------------------
 .../entity_reference.routing.yml              |   7 +
 .../EntityReferenceAutocomplete.php           | 102 ++++++++++++
 .../EntityReferenceBundle.php                 |   7 +-
 .../EntityReferenceController.php             | 100 +++++++++++
 5 files changed, 214 insertions(+), 158 deletions(-)
 create mode 100644 core/modules/entity_reference/entity_reference.routing.yml
 create mode 100644 core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php
 create mode 100644 core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php

diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module
index 3566121c5bf0..58b6063d0b98 100644
--- a/core/modules/entity_reference/entity_reference.module
+++ b/core/modules/entity_reference/entity_reference.module
@@ -5,7 +5,6 @@
  * Provides a field that can reference other entities.
  */
 
-use Symfony\Component\HttpFoundation\JsonResponse;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -63,32 +62,6 @@ function entity_reference_entity_field_info_alter(&$info, $entity_type) {
   }
 }
 
-/**
- * Implements hook_menu().
- */
-function entity_reference_menu() {
-  $items = array();
-
-  $items['entity_reference/autocomplete/single/%/%/%'] = array(
-    'title' => 'Entity Reference Autocomplete',
-    'page callback' => 'entity_reference_autocomplete_callback',
-    'page arguments' => array(2, 3, 4, 5),
-    'access callback' => 'entity_reference_autocomplete_access_callback',
-    'access arguments' => array(2, 3, 4, 5),
-    'type' => MENU_CALLBACK,
-  );
-  $items['entity_reference/autocomplete/tags/%/%/%'] = array(
-    'title' => 'Entity Reference Autocomplete',
-    'page callback' => 'entity_reference_autocomplete_callback',
-    'page arguments' => array(2, 3, 4, 5),
-    'access callback' => 'entity_reference_autocomplete_access_callback',
-    'access arguments' => array(2, 3, 4, 5),
-    'type' => MENU_CALLBACK,
-  );
-
-  return $items;
-}
-
 /**
  * Gets the selection handler for a given entity_reference field.
  *
@@ -485,132 +458,3 @@ function entity_reference_create_instance($entity_type, $bundle, $field_name, $f
     field_create_instance($instance);
   }
 }
-
-/**
- * Menu Access callback for the autocomplete widget.
- *
- * @param string $type
- *   The widget type (i.e. 'single' or 'tags').
- * @param string $field_name
- *   The name of the entity reference field.
- * @param string $entity_type
- *   The entity type.
- * @param string $bundle_name
- *   The bundle name.
- *
- * @return bool
- *   TRUE if user can access this menu item, FALSE otherwise.
- */
-function entity_reference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) {
-  if (!$field = field_info_field($field_name)) {
-    return FALSE;
-  }
-  if (!$instance = field_info_instance($entity_type, $field_name, $bundle_name)){
-    return FALSE;
-  }
-
-  if ($field['type'] != 'entity_reference' || !field_access('edit', $field, $entity_type)) {
-    return FALSE;
-  }
-
-  return TRUE;
-}
-
-/**
- * Menu callback; Autocomplete the label of an entity.
- *
- * @param string $type
- *   The widget type (i.e. 'single' or 'tags').
- * @param string $field_name
- *   The name of the entity reference field.
- * @param string $entity_type
- *   The entity type.
- * @param string $bundle_name
- *   The bundle name.
- * @param string $entity_id
- *   (optional) The entity ID the entity reference field is attached to.
- *   Defaults to ''.
- *
- * @return \Symfony\Component\HttpFoundation\JsonResponse
- */
-function entity_reference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '') {
-  $field = field_info_field($field_name);
-  $instance = field_info_instance($entity_type, $field_name, $bundle_name);
-  $prefix = '';
-
-  // Get the typed string, if exists from the URL.
-  $tags_typed = drupal_container()->get('request')->query->get('q');
-  $tags_typed = drupal_explode_tags($tags_typed);
-  $string = drupal_strtolower(array_pop($tags_typed));
-
-  // The user entered a comma-separated list of entity labels, so we generate a
-  // prefix.
-  if ($type == 'tags' && !empty($string)) {
-    $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
-  }
-
-  return entity_reference_autocomplete_callback_get_matches($field, $instance, $entity_type, $entity_id, $prefix, $string);
-}
-
-/**
- * Returns JSON data based on a given field, instance and search string.
- *
- * This function can be used by other modules that wish to pass a mocked
- * definition of the field or instance.
- *
- * @param array $field
- *   The field array definition.
- * @param array $instance
- *   The instance array definition.
- * @param string $entity_type
- *   The entity type.
- * @param string $entity_id
- *   (optional) The entity ID the entity reference field is attached to.
- *   Defaults to ''.
- * @param string $prefix
- *   (optional) A prefix for all the keys returned by this function.
- * @param string $string
- *   (optional) The label of the entity to query by.
- *
- * @return \Symfony\Component\HttpFoundation\JsonResponse
- *
- * @see entity_reference_autocomplete_callback()
- */
-function entity_reference_autocomplete_callback_get_matches($field, $instance, $entity_type, $entity_id = '', $prefix = '', $string = '') {
-  $target_type = $field['settings']['target_type'];
-  $matches = array();
-  $entity = NULL;
-
-  if ($entity_id !== 'NULL') {
-    $entity = entity_load($entity_type, $entity_id);
-    // @todo: Improve when we have entity_access().
-    $entity_access = $target_type == 'node' ? node_access('view', $entity) : TRUE;
-    if (!$entity || !$entity_access) {
-      return MENU_ACCESS_DENIED;
-    }
-  }
-  $handler = entity_reference_get_selection_handler($field, $instance, $entity);
-
-  if (isset($string)) {
-    // Get an array of matching entities.
-    $match_operator = !empty($instance['widget']['settings']['match_operator']) ? $instance['widget']['settings']['match_operator'] : 'CONTAINS';
-    $entity_labels = $handler->getReferencableEntities($string, $match_operator, 10);
-
-    // Loop through the entities and convert them into autocomplete output.
-    foreach ($entity_labels as $values) {
-      foreach ($values as $entity_id => $label) {
-        $key = "$label ($entity_id)";
-        // Strip things like starting/trailing white spaces, line breaks and
-        // tags.
-        $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key)))));
-        // Names containing commas or quotes must be wrapped in quotes.
-        if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
-          $key = '"' . str_replace('"', '""', $key) . '"';
-        }
-        $matches[$prefix . $key] = '<div class="reference-autocomplete">' . $label . '</div>';
-      }
-    }
-  }
-
-  return new JsonResponse($matches);
-}
diff --git a/core/modules/entity_reference/entity_reference.routing.yml b/core/modules/entity_reference/entity_reference.routing.yml
new file mode 100644
index 000000000000..e0b23b873e5e
--- /dev/null
+++ b/core/modules/entity_reference/entity_reference.routing.yml
@@ -0,0 +1,7 @@
+entity_reference.autocomplete:
+  pattern: '/entity_reference/autocomplete/{type}/{field_name}/{entity_type}/{bundle_name}/{entity_id}'
+  defaults:
+    _controller: '\Drupal\entity_reference\EntityReferenceController::handleAutocomplete'
+    entity_id: 'NULL'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php
new file mode 100644
index 000000000000..a5b41afc23a3
--- /dev/null
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_reference/EntityReferenceAutocomplete.
+ */
+namespace Drupal\entity_reference;
+
+use Drupal\Core\Entity\EntityManager;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Helper class to get autocompletion results for entity reference.
+ */
+class EntityReferenceAutocomplete {
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a EntityReferenceAutocomplete object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManager $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * Returns matched labels based on a given field, instance and search string.
+   *
+   * This function can be used by other modules that wish to pass a mocked
+   * definition of the field on instance.
+   *
+   * @param array $field
+   *   The field array definition.
+   * @param array $instance
+   *   The instance array definition.
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $entity_id
+   *   (optional) The entity ID the entity reference field is attached to.
+   *   Defaults to ''.
+   * @param string $prefix
+   *   (optional) A prefix for all the keys returned by this function.
+   * @param string $string
+   *   (optional) The label of the entity to query by.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when the current user doesn't have access to the specifies entity.
+   *
+   * @return array
+   *   A list of matched entity labels.
+   *
+   * @see \Drupal\entity_reference\EntityReferenceController
+   */
+  public function getMatches($field, $instance, $entity_type, $entity_id = '', $prefix = '', $string = '') {
+    $target_type = $field['settings']['target_type'];
+    $matches = array();
+    $entity = NULL;
+
+    if ($entity_id !== 'NULL') {
+      $entities = $this->entityManager->getStorageController($entity_type)->load(array($entity_id));
+      $entity = reset($entities);
+      // @todo: Improve when we have entity_access().
+      $entity_access = $target_type == 'node' ? node_access('view', $entity) : TRUE;
+      if (!$entity || !$entity_access) {
+        throw new AccessDeniedHttpException();
+      }
+    }
+    $handler = entity_reference_get_selection_handler($field, $instance, $entity);
+
+    if (isset($string)) {
+      // Get an array of matching entities.
+      $match_operator = !empty($instance['widget']['settings']['match_operator']) ? $instance['widget']['settings']['match_operator'] : 'CONTAINS';
+      $entity_labels = $handler->getReferencableEntities($string, $match_operator, 10);
+
+      // Loop through the entities and convert them into autocomplete output.
+      foreach ($entity_labels as $values) {
+        foreach ($values as $entity_id => $label) {
+          $key = "$label ($entity_id)";
+          // Strip things like starting/trailing white spaces, line breaks and
+          // tags.
+          $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key)))));
+          // Names containing commas or quotes must be wrapped in quotes.
+          if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
+            $key = '"' . str_replace('"', '""', $key) . '"';
+          }
+          $matches[$prefix . $key] = '<div class="reference-autocomplete">' . $label . '</div>';
+        }
+      }
+    }
+
+    return $matches;
+  }
+
+}
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceBundle.php b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceBundle.php
index 56bd79737056..f9762b68c980 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceBundle.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceBundle.php
@@ -8,6 +8,7 @@
 namespace Drupal\entity_reference;
 
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\HttpKernel\Bundle\Bundle;
 
 /**
@@ -19,9 +20,11 @@ class EntityReferenceBundle extends Bundle {
    * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
    */
   public function build(ContainerBuilder $container) {
-    // Register the SelectionPluginManager class with the dependency injection
-    // container.
+    // Register the SelectionPluginManager class and the autocomplete helper
+    // with the dependency injection container.
     $container->register('plugin.manager.entity_reference.selection', 'Drupal\entity_reference\Plugin\Type\SelectionPluginManager')
       ->addArgument('%container.namespaces%');
+    $container->register('entity_reference.autocomplete', 'Drupal\entity_reference\EntityReferenceAutocomplete')
+      ->addArgument(new Reference('plugin.manager.entity'));
   }
 }
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php
new file mode 100644
index 000000000000..1301dc3f3d14
--- /dev/null
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_reference/EntityReferenceController.
+ */
+
+namespace Drupal\entity_reference;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Drupal\Core\ControllerInterface;
+
+/**
+ * Defines route controller for entity reference.
+ */
+class EntityReferenceController implements ControllerInterface {
+
+  /**
+   * The autocomplete helper for entity references.
+   *
+   * @var \Drupal\entity_reference\EntityReferenceAutocomplete
+   */
+  protected $entityReferenceAutocomplete;
+
+  /**
+   * Constructs a EntityReferenceController object.
+   *
+   * @param \Drupal\entity_reference\EntityReferenceAutocomplete $entity_reference_autcompletion
+   *   The autocompletion helper for entity references
+   */
+  public function __construct(EntityReferenceAutocomplete $entity_reference_autcompletion) {
+    $this->entityReferenceAutocomplete = $entity_reference_autcompletion;
+  }
+
+  /**
+   * Implements \Drupal\Core\ControllerInterface::create().
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_reference.autocomplete')
+    );
+  }
+
+  /**
+   * Autocomplete the label of an entity.
+   *
+   * @param Request $request
+   *   The request object that contains the typed tags.
+   * @param string $type
+   *   The widget type (i.e. 'single' or 'tags').
+   * @param string $field_name
+   *   The name of the entity reference field.
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $bundle_name
+   *   The bundle name.
+   * @param string $entity_id
+   *   (optional) The entity ID the entity reference field is attached to.
+   *   Defaults to ''.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Throws access denied when either the field or field instance does not
+   *   exists or the user does not have access to edit the field.
+   *
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   The matched labels as json.
+   */
+  public function handleAutocomplete(Request $request, $type, $field_name, $entity_type, $bundle_name, $entity_id) {
+    if (!$field = field_info_field($field_name)) {
+      throw new AccessDeniedHttpException();
+    }
+
+    if (!$instance = field_info_instance($entity_type, $field_name, $bundle_name)) {
+      throw new AccessDeniedHttpException();
+    }
+
+    if ($field['type'] != 'entity_reference' || !field_access('edit', $field, $entity_type)) {
+      throw new AccessDeniedHttpException();
+    }
+
+    // Get the typed string, if exists from the URL.
+    $items_typed = $request->query->get('q');
+    $items_typed = drupal_explode_tags($items_typed);
+    $last_item = drupal_strtolower(array_pop($items_typed));
+
+    $prefix = '';
+    // The user entered a comma-separated list of entity labels, so we generate
+    // a prefix.
+    if ($type == 'tags' && !empty($last_item)) {
+      $prefix = count($items_typed) ? drupal_implode_tags($items_typed) . ', ' : '';
+    }
+
+    $matches = $this->entityReferenceAutocomplete->getMatches($field, $instance, $entity_type, $entity_id, $prefix, $last_item);
+
+    return new JsonResponse($matches);
+  }
+}
-- 
GitLab