diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyAutocomplete.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyAutocomplete.php new file mode 100644 index 0000000..2066743 --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyAutocomplete.php @@ -0,0 +1,104 @@ +connection = $connection; + } + + /** + * Get matches for the autocompletion of taxonomy terms. + * + * @param string $field_name + * The name of the term reference field. + * @param string $tags_typed + * The input string from the user. + * + * @throws \Exception + * + * @return array + * The matches as array. + * + * @see taxonomy_field_widget_info() + */ + public function getMatches($field_name, $tags_typed = '') { + + // Make sure the field exists and is a taxonomy field. + if (!($field = field_info_field($field_name)) || $field['type'] !== 'taxonomy_term_reference') { + // Error string. The JavaScript handler will realize this is not JSON and + // will display it as debugging information. + throw new \Exception(t('Taxonomy field @field_name not found.', array('@field_name' => $field_name))); + } + + // The user enters a comma-separated list of tags. We only autocomplete + // the last tag. + $tags_typed = drupal_explode_tags($tags_typed); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + + $term_matches = array(); + if ($tag_last != '') { + + // Part of the criteria for the query come from the field's own settings. + $vids = array(); + foreach ($field['settings']['allowed_values'] as $tree) { + $vids[] = $tree['vocabulary']; + } + + $query = $this->connection->select('taxonomy_term_data', 't'); + $query->addTag('translatable'); + $query->addTag('term_access'); + + // Do not select already entered terms. + if (!empty($tags_typed)) { + $query->condition('t.name', $tags_typed, 'NOT IN'); + } + // Select rows that match by term name. + $tags_return = $query + ->fields('t', array('tid', 'name')) + ->condition('t.vid', $vids) + ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE') + ->range(0, 10) + ->execute() + ->fetchAllKeyed(); + + $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; + + foreach ($tags_return as $tid => $name) { + $n = $name; + // Term names containing commas or quotes must be wrapped in quotes. + if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) { + $n = '"' . str_replace('"', '""', $name) . '"'; + } + $term_matches[$prefix . $n] = check_plain($name); + } + } + return $term_matches; + } + +} diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyAutocompleteController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyAutocompleteController.php new file mode 100644 index 0000000..58e14fe --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyAutocompleteController.php @@ -0,0 +1,73 @@ +request = $request; + $this->taxonomyAutocomplete = $taxonomy_autocomplete; + } + + /** + * Autocompletes a taxonomy term. + * + * This function outputs term name suggestions in response to Ajax requests + * made by the taxonomy autocomplete widget for taxonomy term reference + * fields. The output is a JSON object of plain-text term suggestions, keyed + * by the user-entered value with the completed term name appended. Term + * names containing commas are wrapped in quotes. + * + * For example, suppose the user has entered the string 'red fish, blue' in + * the field, and there are two taxonomy terms, 'blue fish' and 'blue moon'. + * The JSON output would have the following structure: + * @code + * { + * "red fish, blue fish": "blue fish", + * "red fish, blue moon": "blue moon", + * }; + * @endcode + * + * @param string $field_name + * The name of the term reference field. + * + * @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\Response + * The actual response for the javascript. If the field name exists it will + * be a json response, else a normal response, so the javascript can handle + * the error. + */ + public function autocomplete($field_name) { + $tags_typed = $this->request->query->get('q'); + + try { + $matches = $this->taxonomyAutocomplete->getMatches($field_name, $tags_typed); + return new JsonResponse($matches); + } + catch (\Exception $e) { + return new Response($e->getMessage()); + } + } + +} diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyBundle.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyBundle.php new file mode 100644 index 0000000..e097201 --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TaxonomyBundle.php @@ -0,0 +1,30 @@ +register('taxonomy.autocomplete_controller', 'Drupal\taxonomy\TaxonomyAutocompleteController') + ->addArgument(new Reference('request')) + ->addArgument(new Reference('taxonomy.autocomplete')); + $container->register('taxonomy.autocomplete', 'Drupal\taxonomy\TaxonomyAutocomplete') + ->addArgument(new Reference('database')); + } + +} diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module index 16d1019..96a3aeb 100644 --- a/core/modules/taxonomy/taxonomy.module +++ b/core/modules/taxonomy/taxonomy.module @@ -321,13 +321,16 @@ function taxonomy_menu() { 'type' => MENU_CALLBACK, 'file' => 'taxonomy.pages.inc', ); + + // @todo Remove once drupal_valid_path() is fixed to find and access check + // paths managed by the new routing system: http://drupal.org/node/1793520. $items['taxonomy/autocomplete/%'] = array( 'title' => 'Autocomplete taxonomy', - 'page callback' => 'taxonomy_autocomplete', + // _menu_router_build() denies access to paths without a page callback. + 'page callback' => 'NOT_USED', 'page arguments' => array(2), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, - 'file' => 'taxonomy.pages.inc', ); $items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array( diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc index fbd29f1..75558af 100644 --- a/core/modules/taxonomy/taxonomy.pages.inc +++ b/core/modules/taxonomy/taxonomy.pages.inc @@ -77,87 +77,3 @@ function taxonomy_term_feed(Term $term) { return node_feed($nids, $channel); } - -/** - * Page callback: Outputs JSON for taxonomy autocomplete suggestions. - * - * This callback outputs term name suggestions in response to Ajax requests - * made by the taxonomy autocomplete widget for taxonomy term reference - * fields. The output is a JSON object of plain-text term suggestions, keyed by - * the user-entered value with the completed term name appended. Term names - * containing commas are wrapped in quotes. - * - * For example, suppose the user has entered the string 'red fish, blue' in the - * field, and there are two taxonomy terms, 'blue fish' and 'blue moon'. The - * JSON output would have the following structure: - * @code - * { - * "red fish, blue fish": "blue fish", - * "red fish, blue moon": "blue moon", - * }; - * @endcode - * - * @param $field_name - * The name of the term reference field. - * - * @see taxonomy_menu() - * @see taxonomy_field_widget_info() - */ -function taxonomy_autocomplete($field_name) { - // A comma-separated list of term names entered in the autocomplete form - // element. Only the last term is used for autocompletion. - $tags_typed = drupal_container()->get('request')->query->get('q'); - - // Make sure the field exists and is a taxonomy field. - if (!($field = field_info_field($field_name)) || $field['type'] !== 'taxonomy_term_reference') { - // Error string. The JavaScript handler will realize this is not JSON and - // will display it as debugging information. - print t('Taxonomy field @field_name not found.', array('@field_name' => $field_name)); - exit; - } - - // The user enters a comma-separated list of tags. We only autocomplete the last tag. - $tags_typed = drupal_explode_tags($tags_typed); - $tag_last = drupal_strtolower(array_pop($tags_typed)); - - $matches = array(); - if ($tag_last != '') { - - // Part of the criteria for the query come from the field's own settings. - $vids = array(); - foreach ($field['settings']['allowed_values'] as $tree) { - $vids[] = $tree['vocabulary']; - } - - $query = db_select('taxonomy_term_data', 't'); - $query->addTag('translatable'); - $query->addTag('term_access'); - - // Do not select already entered terms. - if (!empty($tags_typed)) { - $query->condition('t.name', $tags_typed, 'NOT IN'); - } - // Select rows that match by term name. - $tags_return = $query - ->fields('t', array('tid', 'name')) - ->condition('t.vid', $vids) - ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE') - ->range(0, 10) - ->execute() - ->fetchAllKeyed(); - - $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; - - $term_matches = array(); - foreach ($tags_return as $tid => $name) { - $n = $name; - // Term names containing commas or quotes must be wrapped in quotes. - if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) { - $n = '"' . str_replace('"', '""', $name) . '"'; - } - $term_matches[$prefix . $n] = check_plain($name); - } - } - - return new JsonResponse($term_matches); -} diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml new file mode 100644 index 0000000..0aa534c --- /dev/null +++ b/core/modules/taxonomy/taxonomy.routing.yml @@ -0,0 +1,6 @@ +taxonomy_autocomplete: + pattern: '/taxonomy/autocomplete/{field_name}' + defaults: + _controller: 'taxonomy.autocomplete_controller:autocomplete' + requirements: + _permission: 'access content'