Issue #1801356 by dawehner, disasm, amateescu, effulgentsia: Entity reference autocomplete using routes.
parent
4b455afc6d
commit
ef63cffdf3
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue