Commit 2141a64e authored by Dries's avatar Dries

- Patch #497206 by jhodgdon, pwolanin, Island Usurper, YannickD: avoid search...

- Patch #497206 by jhodgdon, pwolanin, Island Usurper, YannickD: avoid search conflicts with other forms, use menu API instead of search_get_keys().
parent 5fcf1eda
......@@ -1539,7 +1539,7 @@ function node_search_admin() {
/**
* Implements hook_search_execute().
*/
function node_search_execute($keys = NULL) {
function node_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');
......
......@@ -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,9 +47,35 @@ 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.
*
......@@ -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');
......
......@@ -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
......@@ -868,21 +868,6 @@ function search_expression_insert($keys, $option, $value = '') {
return $keys;
}
/**
* 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
* @{
......@@ -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);
}
......
......@@ -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;
}
/**
......
......@@ -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;
}
}
; $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
<?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'));
}
......@@ -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),
),
);
}
......@@ -871,7 +871,7 @@ function user_search_access() {
/**
* Implements hook_search_execute().
*/
function user_search_execute($keys = NULL) {
function user_search_execute($keys = NULL, $conditions = NULL) {
$find = array();
// Replace wildcards with MySQL/PostgreSQL wildcards.
$keys = preg_replace('!\*+!', '%', $keys);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment