summaryrefslogtreecommitdiff
path: root/modules/search
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2010-08-11 14:21:39 +0000
committerDries Buytaert <dries@buytaert.net>2010-08-11 14:21:39 +0000
commit2141a64e341b9880b7b32ec7456554170dd21ec6 (patch)
tree42cd00c4e840821295247198452b4f55931f4d88 /modules/search
parent5fcf1eda32adf4851b40360bc8df1157bbdeea54 (diff)
downloadbrdo-2141a64e341b9880b7b32ec7456554170dd21ec6.tar.gz
brdo-2141a64e341b9880b7b32ec7456554170dd21ec6.tar.bz2
- Patch #497206 by jhodgdon, pwolanin, Island Usurper, YannickD: avoid search conflicts with other forms, use menu API instead of search_get_keys().
Diffstat (limited to 'modules/search')
-rw-r--r--modules/search/search.api.php43
-rw-r--r--modules/search/search.module25
-rw-r--r--modules/search/search.pages.inc58
-rw-r--r--modules/search/search.test128
-rw-r--r--modules/search/tests/search_embedded_form.info8
-rw-r--r--modules/search/tests/search_embedded_form.module70
-rw-r--r--modules/search/tests/search_extra_type.module19
7 files changed, 300 insertions, 51 deletions
diff --git a/modules/search/search.api.php b/modules/search/search.api.php
index a33bfaf83..da3b7b839 100644
--- a/modules/search/search.api.php
+++ b/modules/search/search.api.php
@@ -30,9 +30,16 @@
* node_form_search_form_alter() for an example.
*
* @return
- * Array with the optional keys 'title' for the tab title and 'path' for
- * the path component after 'search/'. Both will default to the module
- * name.
+ * Array with optional keys:
+ * - 'title': Title for the tab on the search page for this module. Defaults
+ * to the module name if not given.
+ * - 'path': Path component after 'search/' for searching with this module.
+ * Defaults to the module name if not given.
+ * - 'conditions_callback': Name of a callback function that is invoked by
+ * search_view() to get an array of additional search conditions to pass to
+ * search_data(). For example, a search module may get additional keywords,
+ * filters, or modifiers for the search from the query string. Sample
+ * callback function: sample_search_conditions_callback().
*
* @ingroup search
*/
@@ -40,10 +47,36 @@ function hook_search_info() {
return array(
'title' => 'Content',
'path' => 'node',
+ 'conditions_callback' => 'sample_search_conditions_callback',
);
}
/**
+ * An example conditions callback function for search.
+ *
+ * This example pulls additional search keywords out of the $_REQUEST variable,
+ * (i.e. from the query string of the request). The conditions may also be
+ * generated internally - for example based on a module's settings.
+ *
+ * @see hook_search_info()
+ * @ingroup search
+ */
+function sample_search_conditions_callback($keys) {
+ $conditions = array();
+
+ if (!empty($_REQUEST['keys'])) {
+ $conditions['keys'] = $_REQUEST['keys'];
+ }
+ if (!empty($_REQUEST['sample_search_keys'])) {
+ $conditions['sample_search_keys'] = $_REQUEST['sample_search_keys'];
+ }
+ if ($force_keys = variable_get('sample_search_force_keywords', '')) {
+ $conditions['sample_search_force_keywords'] = $force_keys;
+ }
+ return $conditions;
+}
+
+/**
* Define access to a custom search routine.
*
* This hook allows a module to define permissions for a search tab.
@@ -139,6 +172,8 @@ function hook_search_admin() {
*
* @param $keys
* The search keywords as entered by the user.
+ * @param $conditions
+ * An optional array of additional conditions, such as filters.
*
* @return
* An array of search results. To use the default search result
@@ -154,7 +189,7 @@ function hook_search_admin() {
*
* @ingroup search
*/
-function hook_search_execute($keys = NULL) {
+function hook_search_execute($keys = NULL, $conditions = NULL) {
// Build matching conditions
$query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault');
$query->join('node', 'n', 'n.nid = i.sid');
diff --git a/modules/search/search.module b/modules/search/search.module
index 82d6a1b12..981256dc4 100644
--- a/modules/search/search.module
+++ b/modules/search/search.module
@@ -208,7 +208,7 @@ function search_menu() {
$items[$path] = array(
'title' => $search_info['title'],
'page callback' => 'search_view',
- 'page arguments' => array($module),
+ 'page arguments' => array($module, ''),
'access callback' => '_search_menu_access',
'access arguments' => array($module),
'type' => MENU_LOCAL_TASK,
@@ -218,7 +218,7 @@ function search_menu() {
$items["$path/%menu_tail"] = array(
'title' => $search_info['title'],
'page callback' => 'search_view',
- 'page arguments' => array($module),
+ 'page arguments' => array($module, 2),
'access callback' => '_search_menu_access',
'access arguments' => array($module),
// The default local task points to its parent, but this item points to
@@ -869,21 +869,6 @@ function search_expression_insert($keys, $option, $value = '') {
}
/**
- * Helper function for grabbing search keys.
- */
-function search_get_keys() {
- $return = &drupal_static(__FUNCTION__);
- if (!isset($return)) {
- // Extract keys as remainder of path
- // Note: support old GET format of searches for existing links.
- $path = explode('/', $_GET['q'], 3);
- $keys = empty($_REQUEST['keys']) ? '' : $_REQUEST['keys'];
- $return = count($path) == 3 ? $path[2] : $keys;
- }
- return $return;
-}
-
-/**
* @defgroup search Search interface
* @{
* The Drupal search interface manages a global search mechanism.
@@ -1052,14 +1037,16 @@ function template_preprocess_search_block_form(&$variables) {
* Keyword query to search on.
* @param $module
* Search module to search.
+ * @param $conditions
+ * Optional array of additional search conditions.
*
* @return
* Formatted search results. No return value if $keys are not supplied or
* if the given search module is not active.
*/
-function search_data($keys, $module) {
+function search_data($keys, $module, $conditions = NULL) {
if (module_hook($module, 'search_execute')) {
- $results = module_invoke($module, 'search_execute', $keys);
+ $results = module_invoke($module, 'search_execute', $keys, $conditions);
if (module_hook($module, 'search_page')) {
return module_invoke($module, 'search_page', $results);
}
diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc
index 0d9fb4bc3..a5eca2d21 100644
--- a/modules/search/search.pages.inc
+++ b/modules/search/search.pages.inc
@@ -11,10 +11,18 @@
*
* @param $module
* Search module to use for the search.
+ * @param $keys
+ * Keywords to use for the search.
*/
-function search_view($module = NULL) {
+function search_view($module = NULL, $keys = '') {
$info = FALSE;
$redirect = FALSE;
+ $keys = trim($keys);
+ // Also try to pull search keywords out of the $_REQUEST variable to
+ // support old GET format of searches for existing links.
+ if (!$keys && !empty($_REQUEST['keys'])) {
+ $keys = trim($_REQUEST['keys']);
+ }
if (!empty($module)) {
$active_module_info = search_get_info();
@@ -28,40 +36,40 @@ function search_view($module = NULL) {
// are no enabled search modules, this function should never be called,
// since hook_menu() would not have defined any search paths.
$info = search_get_default_module_info();
- $redirect = TRUE;
+ // Redirect from bare /search or an invalid path to the default search path.
+ $path = 'search/' . $info['path'];
+ if ($keys) {
+ $path .= '/' . $keys;
+ }
+ drupal_goto($path);
}
- // Search form submits with POST but redirects to GET. This way we can keep
- // the search query URL clean as a whistle:
- // search/type_path/keyword+keyword
- if (!isset($_POST['form_id'])) {
- $keys = search_get_keys();
- if ($redirect) {
- // Redirect from bare /search or an invalid path to the default search path.
- $path = 'search/' . $info['path'];
- if ($keys) {
- $path .= '/' . $keys;
- }
- drupal_goto($path);
+ $results = '';
+ // Process the search form. Note that if there is $_POST data,
+ // search_form_submit() will cause a redirect to search/[module path]/[keys],
+ // which will get us back to this page callback. In other words, the search
+ // form submits with POST but redirects to GET. This way we can keep
+ // the search query URL clean as a whistle.
+ if (empty($_POST['form_id']) || $_POST['form_id'] != 'search_form') {
+ $conditions = NULL;
+ if (isset($info['conditions_callback']) && function_exists($info['conditions_callback'])) {
+ // Build an optional array of more search conditions.
+ $conditions = call_user_func($info['conditions_callback'], $keys);
}
- // Only perform search if there is a non-whitespace search term.
- $results = '';
- if (trim($keys)) {
+ // Only search if there are keywords or non-empty conditions.
+ if ($keys || !empty($conditions)) {
// Log the search keys.
watchdog('search', 'Searched %type for %keys.', array('%keys' => $keys, '%type' => $info['title']), WATCHDOG_NOTICE, l(t('results'), 'search/' . $info['path'] . '/' . $keys));
// Collect the search results.
- $results = search_data($keys, $info['module']);
-
- // Construct the search form.
- $build['search_form'] = drupal_get_form('search_form', NULL, $keys, $info['module']);
- $build['search_results'] = array('#markup' => $results);
-
- return $build;
+ $results = search_data($keys, $info['module'], $conditions);
}
}
+ // The form may be altered based on whether the search was run.
+ $build['search_form'] = drupal_get_form('search_form', NULL, $keys, $info['module']);
+ $build['search_results'] = array('#markup' => $results);
- return drupal_get_form('search_form', NULL, empty($keys) ? '' : $keys, $info['module']);
+ return $build;
}
/**
diff --git a/modules/search/search.test b/modules/search/search.test
index c43f8d7d2..ded36a993 100644
--- a/modules/search/search.test
+++ b/modules/search/search.test
@@ -988,6 +988,58 @@ class SearchSimplifyTestCase extends DrupalWebTestCase {
}
}
+
+/**
+ * Test config page.
+ */
+class SearchKeywordsConditions extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Keywords and conditions',
+ 'description' => 'Verify the search pulls in keywords and extra conditions.',
+ 'group' => 'Search',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('search', 'search_extra_type');
+ // Create searching user.
+ $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'post comments without approval'));
+ // Login with sufficient privileges.
+ $this->drupalLogin($this->searching_user);
+ // Test with all search modules enabled.
+ variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type'));
+ menu_rebuild();
+ }
+
+ /**
+ * Verify the kewords are captured and conditions respected.
+ */
+ function testSearchKeyswordsConditions() {
+ // No keys, not conditions - no results.
+ $this->drupalGet('search/dummy_path');
+ $this->assertNoText('Dummy search snippet to display');
+ // With keys - get results.
+ $keys = 'bike shed ' . $this->randomName();
+ $this->drupalGet("search/dummy_path/{$keys}");
+ $this->assertText("Dummy search snippet to display. Keywords: {$keys}");
+ $keys = 'blue drop ' . $this->randomName();
+ $this->drupalGet("search/dummy_path", array('query' => array('keys' => $keys)));
+ $this->assertText("Dummy search snippet to display. Keywords: {$keys}");
+ // Add some conditions and keys.
+ $keys = 'moving drop ' . $this->randomName();
+ $this->drupalGet("search/dummy_path/bike", array('query' => array('search_conditions' => $keys)));
+ $this->assertText("Dummy search snippet to display.");
+ $this->assertRaw(print_r(array('search_conditions' => $keys), TRUE));
+ // Add some conditions and no keys.
+ $keys = 'drop kick ' . $this->randomName();
+ $this->drupalGet("search/dummy_path", array('query' => array('search_conditions' => $keys)));
+ $this->assertText("Dummy search snippet to display.");
+ $this->assertRaw(print_r(array('search_conditions' => $keys), TRUE));
+ }
+}
+
/**
* Test config page.
*/
@@ -1335,3 +1387,79 @@ class SearchTokenizerTestCase extends DrupalWebTestCase {
return '';
}
}
+
+/**
+ * Tests that we can embed a form in search results and submit it.
+ */
+class SearchEmbedForm extends DrupalWebTestCase {
+ /**
+ * Node used for testing.
+ */
+ public $node;
+
+ /**
+ * Count of how many times the form has been submitted.
+ */
+ public $submit_count = 0;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Embedded forms',
+ 'description' => 'Verifies that a form embedded in search results works',
+ 'group' => 'Search',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('search', 'search_embedded_form');
+
+ // Create a user and a node, and update the search index.
+ $test_user = $this->drupalCreateUser(array('access content', 'search content', 'administer nodes'));
+ $this->drupalLogin($test_user);
+
+ $this->node = $this->drupalCreateNode();
+
+ node_update_index();
+ search_update_totals();
+
+ // Set up a dummy initial count of times the form has been submitted.
+ $this->submit_count = 12;
+ variable_set('search_embedded_form_submitted', $this->submit_count);
+ $this->refreshVariables();
+ }
+
+ /**
+ * Tests that the embedded form appears and can be submitted.
+ */
+ function testEmbeddedForm() {
+ // First verify we can submit the form from the module's page.
+ $this->drupalPost('search_embedded_form',
+ array('name' => 'John'),
+ t('Send away'));
+ $this->assertText(t('Test form was submitted'), 'Form message appears');
+ $count = variable_get('search_embedded_form_submitted', 0);
+ $this->assertEqual($this->submit_count + 1, $count, 'Form submission count is correct');
+ $this->submit_count = $count;
+
+ // Now verify that we can see and submit the form from the search results.
+ $this->drupalGet('search/node/' . $this->node->title);
+ $this->assertText(t('Your name'), 'Form is visible');
+ $this->drupalPost('search/node/' . $this->node->title,
+ array('name' => 'John'),
+ t('Send away'));
+ $this->assertText(t('Test form was submitted'), 'Form message appears');
+ $count = variable_get('search_embedded_form_submitted', 0);
+ $this->assertEqual($this->submit_count + 1, $count, 'Form submission count is correct');
+ $this->submit_count = $count;
+
+ // Now verify that if we submit the search form, it doesn't count as
+ // our form being submitted.
+ $this->drupalPost('search',
+ array('keys' => 'foo'),
+ t('Search'));
+ $this->assertNoText(t('Test form was submitted'), 'Form message does not appear');
+ $count = variable_get('search_embedded_form_submitted', 0);
+ $this->assertEqual($this->submit_count, $count, 'Form submission count is correct');
+ $this->submit_count = $count;
+ }
+}
diff --git a/modules/search/tests/search_embedded_form.info b/modules/search/tests/search_embedded_form.info
new file mode 100644
index 000000000..54455134b
--- /dev/null
+++ b/modules/search/tests/search_embedded_form.info
@@ -0,0 +1,8 @@
+; $Id$
+name = "Search embedded form"
+description = "Support module for search module testing of embedded forms."
+package = Testing
+version = VERSION
+core = 7.x
+files[] = search_embedded_form.module
+hidden = TRUE
diff --git a/modules/search/tests/search_embedded_form.module b/modules/search/tests/search_embedded_form.module
new file mode 100644
index 000000000..d80fa170a
--- /dev/null
+++ b/modules/search/tests/search_embedded_form.module
@@ -0,0 +1,70 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Test module implementing a form that can be embedded in search results.
+ *
+ * Embedded form are important, for example, for ecommerce sites where each
+ * search result may included an embedded form with buttons like "Add to cart"
+ * for each individual product (node) listed in the search results.
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function search_embedded_form_menu() {
+ $items['search_embedded_form'] = array(
+ 'title' => 'Search_Embed_Form',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('search_embedded_form_form'),
+ 'access arguments' => array('search content'),
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Builds a form for embedding in search results for testing.
+ *
+ * @see search_embedded_form_form_submit().
+ */
+function search_embedded_form_form($form, &$form_state) {
+ $count = variable_get('search_embedded_form_submitted', 0);
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Your name'),
+ '#maxlength' => 255,
+ '#default_value' => '',
+ '#required' => TRUE,
+ '#description' => t('Times form has been submitted: %count', array('%count' => $count)),
+ );
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Send away'),
+ );
+
+ $form['#submit'][] = 'search_embedded_form_form_submit';
+
+ return $form;
+}
+
+/**
+ * Submit handler for search_embedded_form_form().
+ */
+function search_embedded_form_form_submit($form, &$form_state) {
+ $count = variable_get('search_embedded_form_submitted', 0) + 1;
+ variable_set('search_embedded_form_submitted', $count);
+ drupal_set_message(t('Test form was submitted'));
+}
+
+/**
+ * Adds the test form to search results.
+ */
+function search_embedded_form_preprocess_search_result(&$variables) {
+ $variables['snippet'] .= drupal_render(drupal_get_form('search_embedded_form_form'));
+}
diff --git a/modules/search/tests/search_extra_type.module b/modules/search/tests/search_extra_type.module
index 0aaa9260a..1d22c46c8 100644
--- a/modules/search/tests/search_extra_type.module
+++ b/modules/search/tests/search_extra_type.module
@@ -13,22 +13,35 @@ function search_extra_type_search_info() {
return array(
'title' => 'Dummy search type',
'path' => 'dummy_path',
+ 'conditions_callback' => 'search_extra_type_conditions',
);
}
+function search_extra_type_conditions() {
+ $conditions = array();
+
+ if (!empty($_REQUEST['search_conditions'])) {
+ $conditions['search_conditions'] = $_REQUEST['search_conditions'];
+ }
+ return $conditions;
+}
+
/**
* Implements hook_search_execute().
*
* This is a dummy search, so when search "executes", we just return a dummy
- * result.
+ * result containing the keywords and a list of conditions.
*/
-function search_extra_type_search_execute() {
+function search_extra_type_search_execute($keys = NULL, $conditions = NULL) {
+ if (!$keys) {
+ $keys = '';
+ }
return array(
array(
'link' => url('node'),
'type' => 'Dummy result type',
'title' => 'Dummy title',
- 'snippet' => 'Dummy search snippet to display',
+ 'snippet' => "Dummy search snippet to display. Keywords: {$keys}\n\nConditions: " . print_r($conditions, TRUE),
),
);
}