diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module index f5e968c..b2ab709 100644 --- a/core/modules/entity_reference/entity_reference.module +++ b/core/modules/entity_reference/entity_reference.module @@ -63,18 +63,16 @@ function entity_reference_menu() { $items['entity_reference/autocomplete/single/%/%/%'] = array( 'title' => 'Entity Reference Autocomplete', - 'page callback' => 'entity_reference_autocomplete_callback', + 'page callback' => 'NOT_USED', 'page arguments' => array(2, 3, 4, 5), - 'access callback' => 'entity_reference_autocomplete_access_callback', - 'access arguments' => array(2, 3, 4, 5), + 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); $items['entity_reference/autocomplete/tags/%/%/%'] = array( 'title' => 'Entity Reference Autocomplete', - 'page callback' => 'entity_reference_autocomplete_callback', + 'page callback' => 'NOT_USED', 'page arguments' => array(2, 3, 4, 5), - 'access callback' => 'entity_reference_autocomplete_access_callback', - 'access arguments' => array(2, 3, 4, 5), + 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); @@ -477,132 +475,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] = '
' . $label . '
'; - } - } - } - - 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 0000000..e0b23b8 --- /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 0000000..a5b41af --- /dev/null +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceAutocomplete.php @@ -0,0 +1,102 @@ +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] = '
' . $label . '
'; + } + } + } + + 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 56bd797..f9762b6 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 0000000..1301dc3 --- /dev/null +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/EntityReferenceController.php @@ -0,0 +1,100 @@ +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); + } +}