From f1b6874e72a24f73867567544d7a3f66db6e5ce7 Mon Sep 17 00:00:00 2001
From: Dries <dries@buytaert.net>
Date: Sun, 2 Dec 2012 22:23:33 -0500
Subject: [PATCH] Issue #1831324 by sun, effulgentsia: Form API
 #autocomplete_path() JS and callbacks to use GET q= parameter instead of menu
 tail.

---
 core/includes/form.inc                        | 25 +------------------
 core/misc/autocomplete.js                     |  8 +++---
 .../Drupal/system/Tests/Theme/FastTest.php    |  2 +-
 .../lib/Drupal/taxonomy/Tests/TermTest.php    | 21 +++++++---------
 core/modules/taxonomy/taxonomy.module         |  3 ++-
 core/modules/taxonomy/taxonomy.pages.inc      | 15 +++--------
 .../user/Tests/UserAutocompleteTest.php       |  4 +--
 core/modules/user/user.pages.inc              |  4 +--
 core/modules/views/includes/ajax.inc          | 16 ++++++------
 core/modules/views/views.module               |  2 +-
 core/modules/views/views_ui/admin.inc         |  5 +++-
 11 files changed, 39 insertions(+), 66 deletions(-)

diff --git a/core/includes/form.inc b/core/includes/form.inc
index f9ee3a768ed3..f5f5b980664f 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -3890,36 +3890,13 @@ function theme_vertical_tabs($variables) {
 /**
  * Adds autocomplete functionality to elements with a valid #autocomplete_path.
  *
- * Note that autocomplete callbacks should include special handling as the
- * user's input may contain forward slashes. If the user-submitted string has a
- * '/' in the text that is sent in the autocomplete request, the menu system
- * will split the text and pass it to the callback as multiple arguments.
- *
  * Suppose your autocomplete path in the menu system is 'mymodule_autocomplete.'
  * In your form you have:
  * @code
  * '#autocomplete_path' => 'mymodule_autocomplete/' . $some_key . '/' . $some_id,
  * @endcode
  * The user types in "keywords" so the full path called is:
- * 'mymodule_autocomplete/$some_key/$some_id/keywords'
- *
- * You should include code similar to the following to handle slashes in the
- * input:
- * @code
- * function mymodule_autocomplete_callback($arg1, $arg2, $keywords) {
- *   $args = func_get_args();
- *   // We need to remove $arg1 and $arg2 from the beginning of the array so we
- *   // are left with the keywords.
- *   array_shift($args);
- *   array_shift($args);
- *   // We store the user's original input in $keywords, including any slashes.
- *   // Note: A prepended or trailing slash will be removed. For example, if the
- *   // user enters '/a/few/words/' then $keywords will contain 'a/few/words'.
- *   $keywords = implode('/', $args);
- *
- *   // Your code here.
- * }
- * @endcode
+ * 'mymodule_autocomplete/$some_key/$some_id?q=keywords'
  *
  * @param $element
  *   The form element to process. Properties used:
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index dc58b19e962d..d272c6c90cb5 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -297,11 +297,13 @@ Drupal.ACDB.prototype.search = function (searchString) {
   this.timer = setTimeout(function () {
     db.owner.setStatus('begin');
 
-    // Ajax GET request for autocompletion. We use Drupal.encodePath instead of
-    // encodeURIComponent to allow autocomplete search terms to contain slashes.
+    // Ajax GET request for autocompletion.
     $.ajax({
       type: 'GET',
-      url: db.uri + '/' + Drupal.encodePath(searchString),
+      url: db.uri,
+      data: {
+        q: searchString
+      },
       dataType: 'json',
       success: function (matches) {
         if (typeof matches.status === 'undefined' || matches.status !== 0) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php
index 91ed65ca65f7..843106e4d883 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/FastTest.php
@@ -39,7 +39,7 @@ function setUp() {
    */
   function testUserAutocomplete() {
     $this->drupalLogin($this->account);
-    $this->drupalGet('user/autocomplete/' . $this->account->name);
+    $this->drupalGet('user/autocomplete', array('query' => array('q' => $this->account->name)));
     $this->assertRaw($this->account->name);
     $this->assertNoText('registry initialized', 'The registry was not initialized');
   }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
index 4abbf62d5f2f..3c47f15e845f 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php
@@ -214,13 +214,13 @@ function testNodeTermCreationAndDeletion() {
     // Test autocomplete on term 3, which contains a comma.
     // The term will be quoted, and the " will be encoded in unicode (\u0022).
     $input = substr($term_objects['term3']->name, 0, 3);
-    $json = $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
+    $json = $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name, array('query' => array('q' => $input)));
     $this->assertEqual($json, '{"\u0022' . $term_objects['term3']->name . '\u0022":"' . $term_objects['term3']->name . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term3']->name)));
 
     // Test autocomplete on term 4 - it is alphanumeric only, so no extra
     // quoting.
     $input = substr($term_objects['term4']->name, 0, 3);
-    $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
+    $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name, array('query' => array('q' => $input)));
     $this->assertRaw('{"' . $term_objects['term4']->name . '":"' . $term_objects['term4']->name . '"}', format_string('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term4']->name)));
 
     // Test taxonomy autocomplete with a nonexistent field.
@@ -228,7 +228,7 @@ function testNodeTermCreationAndDeletion() {
     $tag = $this->randomName();
     $message = t("Taxonomy field @field_name not found.", array('@field_name' => $field_name));
     $this->assertFalse(field_info_field($field_name), format_string('Field %field_name does not exist.', array('%field_name' => $field_name)));
-    $this->drupalGet('taxonomy/autocomplete/' . $field_name . '/' . $tag);
+    $this->drupalGet('taxonomy/autocomplete/' . $field_name, array('query' => array('q' => $tag)));
     $this->assertRaw($message, 'Autocomplete returns correct error message when the taxonomy field does not exist.');
   }
 
@@ -252,10 +252,9 @@ function testTermAutocompletion() {
     // Try to autocomplete a term name that matches both terms.
     // We should get both term in a json encoded string.
     $input = '10/';
-    $path = 'taxonomy/autocomplete/taxonomy_';
-    $path .= $this->vocabulary->machine_name . '/' . $input;
+    $path = 'taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name;
     // The result order is not guaranteed, so check each term separately.
-    $result = $this->drupalGet($path);
+    $result = $this->drupalGet($path, array('query' => array('q' => $input)));
     $data = drupal_json_decode($result);
     $this->assertEqual($data[$first_term->name], check_plain($first_term->name), 'Autocomplete returned the first matching term');
     $this->assertEqual($data[$second_term->name], check_plain($second_term->name), 'Autocomplete returned the second matching term');
@@ -263,17 +262,15 @@ function testTermAutocompletion() {
     // Try to autocomplete a term name that matches first term.
     // We should only get the first term in a json encoded string.
     $input = '10/16';
-    $url = 'taxonomy/autocomplete/taxonomy_';
-    $url .= $this->vocabulary->machine_name . '/' . $input;
-    $this->drupalGet($url);
+    $path = 'taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name;
+    $this->drupalGet($path, array('query' => array('q' => $input)));
     $target = array($first_term->name => check_plain($first_term->name));
     $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns only the expected matching term.');
 
     // Try to autocomplete a term name with both a comma and a slash.
     $input = '"term with, comma and / a';
-    $url = 'taxonomy/autocomplete/taxonomy_';
-    $url .= $this->vocabulary->machine_name . '/' . $input;
-    $this->drupalGet($url);
+    $path = 'taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name;
+    $this->drupalGet($path, array('query' => array('q' => $input)));
     $n = $third_term->name;
     // Term names containing commas or quotes must be wrapped in quotes.
     if (strpos($third_term->name, ',') !== FALSE || strpos($third_term->name, '"') !== FALSE) {
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 0158e7ee446e..3a1d297b7941 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -307,9 +307,10 @@ function taxonomy_menu() {
     'type' => MENU_CALLBACK,
     'file' => 'taxonomy.pages.inc',
   );
-  $items['taxonomy/autocomplete'] = array(
+  $items['taxonomy/autocomplete/%'] = array(
     'title' => 'Autocomplete taxonomy',
     'page callback' => 'taxonomy_autocomplete',
+    'page arguments' => array(2),
     'access arguments' => array('access content'),
     'type' => MENU_CALLBACK,
     'file' => 'taxonomy.pages.inc',
diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc
index 042263c8fd5f..62202fbed809 100644
--- a/core/modules/taxonomy/taxonomy.pages.inc
+++ b/core/modules/taxonomy/taxonomy.pages.inc
@@ -102,21 +102,14 @@ function taxonomy_term_feed(Term $term) {
  *
  * @param $field_name
  *   The name of the term reference field.
- * @param $tags_typed
- *   (optional) A comma-separated list of term names entered in the
- *   autocomplete form element. Only the last term is used for autocompletion.
- *   Defaults to '' (an empty string).
  *
  * @see taxonomy_menu()
  * @see taxonomy_field_widget_info()
  */
-function taxonomy_autocomplete($field_name, $tags_typed = '') {
-  // If the request has a '/' in the search text, then the menu system will have
-  // split it into multiple arguments, recover the intended $tags_typed.
-  $args = func_get_args();
-  // Shift off the $field_name argument.
-  array_shift($args);
-  $tags_typed = implode('/', $args);
+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') {
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php
index 8f058f3ad753..d225150d691c 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php
@@ -35,13 +35,13 @@ function setUp() {
   function testUserAutocomplete() {
     // Check access from unprivileged user, should be denied.
     $this->drupalLogin($this->unprivileged_user);
-    $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]);
+    $this->drupalGet('user/autocomplete', array('query' => array('q' => $this->unprivileged_user->name[0])));
     $this->assertResponse(403, 'Autocompletion access denied to user without permission.');
 
     // Check access from privileged user.
     $this->drupalLogout();
     $this->drupalLogin($this->privileged_user);
-    $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]);
+    $this->drupalGet('user/autocomplete', array('query' => array('q' => $this->unprivileged_user->name[0])));
     $this->assertResponse(200, 'Autocompletion access allowed.');
 
     // Using first letter of the user's name, make sure the user's full name is in the results.
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 553f45590c07..127a3af49180 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -14,9 +14,9 @@
 /**
  * Menu callback; Retrieve a JSON object containing autocomplete suggestions for existing users.
  */
-function user_autocomplete($string = '') {
+function user_autocomplete() {
   $matches = array();
-  if ($string) {
+  if ($string = drupal_container()->get('request')->query->get('q')) {
     $result = db_select('users')->fields('users', array('name'))->condition('name', db_like($string) . '%', 'LIKE')->range(0, 10)->execute();
     foreach ($result as $account) {
       $matches[$account->name] = check_plain($account->name);
diff --git a/core/modules/views/includes/ajax.inc b/core/modules/views/includes/ajax.inc
index 1dff8334c7ea..ee769a30fcda 100644
--- a/core/modules/views/includes/ajax.inc
+++ b/core/modules/views/includes/ajax.inc
@@ -283,12 +283,15 @@ function views_ajax_form_wrapper($form_id, &$form_state) {
  *
  * @param string $string
  *   (optional) A comma-separated list of user names entered in the
- *   autocomplete form element. Only the last user is used for autocompletion.
- *   Defaults to '' (an empty string).
+ *   autocomplete form element. If not passed, it is taken from the 'q' query
+ *   string parameter.
  *
  * @return Symfony\Component\HttpFoundation\JsonResponse
  */
-function views_ajax_autocomplete_user($string = '') {
+function views_ajax_autocomplete_user($string = NULL) {
+  if (!isset($string)) {
+    $string = drupal_container()->get('request')->query->get('q');
+  }
   // The user enters a comma-separated list of user name. We only autocomplete the last name.
   $array = drupal_explode_tags($string);
 
@@ -328,14 +331,11 @@ function views_ajax_autocomplete_user($string = '') {
  * @param $vid
  *   The vocabulary id of the tags which should be returned.
  *
- * @param $tags_typed
- *   The typed string of the user.
- *
  * @see taxonomy_autocomplete()
  */
-function views_ajax_autocomplete_taxonomy($vid, $tags_typed = '') {
+function views_ajax_autocomplete_taxonomy($vid) {
   // The user enters a comma-separated list of tags. We only autocomplete the last tag.
-  $tags_typed = drupal_explode_tags($tags_typed);
+  $tags_typed = drupal_explode_tags(drupal_container()->get('request')->query->get('q'));
   $tag_last = drupal_strtolower(array_pop($tags_typed));
 
   $matches = array();
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index ac75dd7a0d88..2227e311b15c 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -417,7 +417,7 @@ function views_menu() {
   );
   // Define another taxonomy autocomplete because the default one of drupal
   // does not support a vid a argument anymore
-  $items['admin/views/ajax/autocomplete/taxonomy'] = array(
+  $items['admin/views/ajax/autocomplete/taxonomy/%'] = array(
     'page callback' => 'views_ajax_autocomplete_taxonomy',
     'theme callback' => 'ajax_base_page_theme',
     'access callback' => 'user_access',
diff --git a/core/modules/views/views_ui/admin.inc b/core/modules/views/views_ui/admin.inc
index 7f08067a6e35..67ff2c269e44 100644
--- a/core/modules/views/views_ui/admin.inc
+++ b/core/modules/views/views_ui/admin.inc
@@ -2096,8 +2096,11 @@ function views_ui_edit_display_form_change_theme($form, &$form_state) {
 /**
  * Page callback for views tag autocomplete
  */
-function views_ui_autocomplete_tag($string = '') {
+function views_ui_autocomplete_tag($string = NULL) {
   $matches = array();
+  if (!isset($string)) {
+    $string = drupal_container()->get('request')->query->get('q');
+  }
   // get matches from default views:
   $views = views_get_all_views();
   foreach ($views as $view) {
-- 
GitLab