- <?php
- * @file
- * Enables site-wide keyword searching.
- */
-
- * Matches all 'N' Unicode character classes (numbers)
- */
- define('PREG_CLASS_NUMBERS',
- '\x{30}-\x{39}\x{b2}\x{b3}\x{b9}\x{bc}-\x{be}\x{660}-\x{669}\x{6f0}-\x{6f9}' .
- '\x{966}-\x{96f}\x{9e6}-\x{9ef}\x{9f4}-\x{9f9}\x{a66}-\x{a6f}\x{ae6}-\x{aef}' .
- '\x{b66}-\x{b6f}\x{be7}-\x{bf2}\x{c66}-\x{c6f}\x{ce6}-\x{cef}\x{d66}-\x{d6f}' .
- '\x{e50}-\x{e59}\x{ed0}-\x{ed9}\x{f20}-\x{f33}\x{1040}-\x{1049}\x{1369}-' .
- '\x{137c}\x{16ee}-\x{16f0}\x{17e0}-\x{17e9}\x{17f0}-\x{17f9}\x{1810}-\x{1819}' .
- '\x{1946}-\x{194f}\x{2070}\x{2074}-\x{2079}\x{2080}-\x{2089}\x{2153}-\x{2183}' .
- '\x{2460}-\x{249b}\x{24ea}-\x{24ff}\x{2776}-\x{2793}\x{3007}\x{3021}-\x{3029}' .
- '\x{3038}-\x{303a}\x{3192}-\x{3195}\x{3220}-\x{3229}\x{3251}-\x{325f}\x{3280}-' .
- '\x{3289}\x{32b1}-\x{32bf}\x{ff10}-\x{ff19}');
-
-
- * Matches all 'P' Unicode character classes (punctuation)
- */
- define('PREG_CLASS_PUNCTUATION',
- '\x{21}-\x{23}\x{25}-\x{2a}\x{2c}-\x{2f}\x{3a}\x{3b}\x{3f}\x{40}\x{5b}-\x{5d}' .
- '\x{5f}\x{7b}\x{7d}\x{a1}\x{ab}\x{b7}\x{bb}\x{bf}\x{37e}\x{387}\x{55a}-\x{55f}' .
- '\x{589}\x{58a}\x{5be}\x{5c0}\x{5c3}\x{5f3}\x{5f4}\x{60c}\x{60d}\x{61b}\x{61f}' .
- '\x{66a}-\x{66d}\x{6d4}\x{700}-\x{70d}\x{964}\x{965}\x{970}\x{df4}\x{e4f}' .
- '\x{e5a}\x{e5b}\x{f04}-\x{f12}\x{f3a}-\x{f3d}\x{f85}\x{104a}-\x{104f}\x{10fb}' .
- '\x{1361}-\x{1368}\x{166d}\x{166e}\x{169b}\x{169c}\x{16eb}-\x{16ed}\x{1735}' .
- '\x{1736}\x{17d4}-\x{17d6}\x{17d8}-\x{17da}\x{1800}-\x{180a}\x{1944}\x{1945}' .
- '\x{2010}-\x{2027}\x{2030}-\x{2043}\x{2045}-\x{2051}\x{2053}\x{2054}\x{2057}' .
- '\x{207d}\x{207e}\x{208d}\x{208e}\x{2329}\x{232a}\x{23b4}-\x{23b6}\x{2768}-' .
- '\x{2775}\x{27e6}-\x{27eb}\x{2983}-\x{2998}\x{29d8}-\x{29db}\x{29fc}\x{29fd}' .
- '\x{3001}-\x{3003}\x{3008}-\x{3011}\x{3014}-\x{301f}\x{3030}\x{303d}\x{30a0}' .
- '\x{30fb}\x{fd3e}\x{fd3f}\x{fe30}-\x{fe52}\x{fe54}-\x{fe61}\x{fe63}\x{fe68}' .
- '\x{fe6a}\x{fe6b}\x{ff01}-\x{ff03}\x{ff05}-\x{ff0a}\x{ff0c}-\x{ff0f}\x{ff1a}' .
- '\x{ff1b}\x{ff1f}\x{ff20}\x{ff3b}-\x{ff3d}\x{ff3f}\x{ff5b}\x{ff5d}\x{ff5f}-' .
- '\x{ff65}');
-
-
- * Matches CJK (Chinese, Japanese, Korean) letter-like characters.
- *
- * This list is derived from the "East Asian Scripts" section of
- * http://www.unicode.org/charts/index.html, as well as a comment on
- * http://unicode.org/reports/tr11/tr11-11.html listing some character
- * ranges that are reserved for additional CJK ideographs.
- *
- * The character ranges do not include numbers, punctuation, or symbols, since
- * these are handled separately in search. Note that radicals and strokes are
- * considered symbols. (See
- * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt)
- *
- * @see search_expand_cjk()
- */
- define('PREG_CLASS_CJK', '\x{1100}-\x{11FF}\x{3040}-\x{309F}\x{30A1}-\x{318E}' .
- '\x{31A0}-\x{31B7}\x{31F0}-\x{31FF}\x{3400}-\x{4DBF}\x{4E00}-\x{9FCF}' .
- '\x{A000}-\x{A48F}\x{A4D0}-\x{A4FD}\x{A960}-\x{A97F}\x{AC00}-\x{D7FF}' .
- '\x{F900}-\x{FAFF}\x{FF21}-\x{FF3A}\x{FF41}-\x{FF5A}\x{FF66}-\x{FFDC}' .
- '\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}');
-
-
- * Implements hook_theme().
- */
- function search_theme() {
- return array(
- 'search_results' => array(
- 'variables' => array('results' => NULL, 'module' => NULL),
- 'file' => 'search.pages.inc',
- 'template' => 'templates/search-results',
- 'file' => 'search.theme.inc',
- ),
- );
- }
-
- * Implements hook_permission().
- */
- function search_permission() {
- return array(
- 'administer search' => array(
- 'title' => t('Administer search'),
- ),
- 'search content' => array(
- 'title' => t('Use search'),
- ),
- 'use advanced search' => array(
- 'title' => t('Use advanced search'),
- ),
- );
- }
-
- * Implements hook_views_api().
- */
- function search_views_api() {
- return array(
- 'api' => '3.0',
- 'path' => backdrop_get_path('module', 'search') . '/views',
- );
- }
-
- * Implements hook_block_info().
- */
- function search_block_info() {
- $blocks['form'] = array(
- 'info' => t('Search form'),
- 'description' => t('The search form for searching site content.'),
- );
- return $blocks;
- }
-
- * Implements hook_block_configure().
- */
- function search_block_configure($delta = '', $settings = array()) {
- $form = array();
- if ($delta == 'form') {
- $search_settings = !empty($settings['search'])? $settings['search'] : array();
- $search_settings += array(
- 'label' => '',
- 'label_hide' => FALSE,
- 'placeholder' => '',
- 'button_text' => 'Search',
- );
-
- $form['search'] = array(
- '#type' => 'fieldset',
- '#title' => t('Search form settings'),
- '#collapsible' => TRUE,
- );
-
- $form['search']['label'] = array(
- '#type' => 'textfield',
- '#title' => t('Search box label'),
- '#default_value' => check_plain($search_settings['label']),
- );
- $form['search']['label_hide'] = array(
- '#type' => 'checkbox',
- '#title' => t('Hide the search box label'),
- '#default_value' => $search_settings['label_hide'],
- );
- $form['search']['placeholder'] = array(
- '#type' => 'textfield',
- '#title' => t('Placeholder text'),
- '#default_value' => check_plain(trim($search_settings['placeholder'])),
- '#description' => t('This text will appear in the search box until text is entered.'),
- );
- $form['search']['button_text'] = array(
- '#type' => 'textfield',
- '#title' => t('Button text'),
- '#default_value' => check_plain(trim($search_settings['button_text'])),
- '#description' => t('Leave this field empty to hide the submit button.'),
- );
- }
- return $form;
- }
-
- * Implements hook_block_view().
- */
- function search_block_view($delta = '', $settings = array(), $contexts = array()) {
- if (user_access('search content')) {
- $search_settings = !empty($settings['search'])? $settings['search'] : array();
- $search_settings += array(
- 'label' => '',
- 'label_hide' => TRUE,
- 'placeholder' => '',
- 'button_text' => 'Search',
- );
- $form = backdrop_get_form('search_block_form');
-
-
- if (!$search_settings['label_hide']) {
- $form['search_block_form']['#title_display'] = 'before';
-
-
- if ($search_settings['label'] != '') {
- $form['search_block_form']['#title'] = t(check_plain($search_settings['label']));
- }
- }
-
-
- if ($search_settings['placeholder'] != '') {
- $form['search_block_form']['#attributes']['placeholder'] = t(trim($search_settings['placeholder']));
- }
-
-
- if ($search_settings['button_text'] != 'Search') {
- if ($search_settings['button_text'] == '') {
- $form['actions']['submit']['#access'] = FALSE;
- }
- else {
- $form['actions']['submit']['#value'] = t(check_plain(trim($search_settings['button_text'])));
- }
- }
-
- $block['subject'] = t('Search');
- $block['content'] = $form;
-
- return $block;
- }
- }
-
- * Implements hook_preprocess_block().
- */
- function search_preprocess_block(&$variables) {
- if ($variables['block']->module == 'search' && $variables['block']->delta == 'form') {
- $variables['attributes']['role'] = 'search';
- $variables['content_attributes']['class'][] = 'container-inline';
- }
- }
-
- * Implements hook_menu().
- */
- function search_menu() {
- $items['search'] = array(
- 'title' => 'Search',
- 'page callback' => 'search_view',
- 'access callback' => 'search_is_active',
- 'type' => MENU_SUGGESTED_ITEM,
- 'file' => 'search.pages.inc',
- );
- $items['admin/config/search/settings'] = array(
- 'title' => 'Search settings',
- 'description' => 'Configure relevance settings for search and other indexing options.',
- 'page callback' => 'backdrop_get_form',
- 'page arguments' => array('search_admin_settings'),
- 'access arguments' => array('administer search'),
- 'weight' => -10,
- 'file' => 'search.admin.inc',
- );
- $items['admin/config/search/settings/reindex'] = array(
- 'title' => 'Rebuild search index',
- 'page callback' => 'backdrop_get_form',
- 'page arguments' => array('search_reindex_confirm'),
- 'access arguments' => array('administer search'),
- 'type' => MENU_VISIBLE_IN_BREADCRUMB,
- 'file' => 'search.admin.inc',
- );
-
-
-
-
-
-
-
-
- backdrop_static_reset('search_get_info');
- $default_info = search_get_default_module_info();
- if ($default_info) {
- foreach (search_get_info() as $module => $search_info) {
- $path = 'search/' . $search_info['path'];
- $items[$path] = array(
- 'title' => $search_info['title'],
- 'page callback' => 'search_view',
- 'page arguments' => array($module, ''),
- 'access callback' => '_search_menu_access',
- 'access arguments' => array($module),
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'search.pages.inc',
- 'weight' => $module == $default_info['module'] ? -10 : 0,
- 'menu_name' => 'internal',
- );
- $items["$path/%menu_tail"] = array(
- 'title' => $search_info['title'],
- 'load arguments' => array('%map', '%index'),
- 'page callback' => 'search_view',
- 'page arguments' => array($module, 2),
- 'access callback' => '_search_menu_access',
- 'access arguments' => array($module),
-
-
- 'type' => MENU_LOCAL_TASK,
- 'file' => 'search.pages.inc',
- 'weight' => 0,
-
- 'tab_root' => 'search/' . $default_info['path'] . '/%',
-
- 'tab_parent' => 'search/' . $default_info['path'],
- 'menu_name' => 'internal',
- );
- }
- }
- return $items;
- }
-
- * Determines access for the ?q=search path.
- */
- function search_is_active() {
-
- return user_access('search content') && search_get_info();
- }
-
- * Returns information about available search modules.
- *
- * @param $all
- * If TRUE, information about all enabled modules implementing
- * hook_search_info() will be returned. If FALSE (default), only modules that
- * have been set to active on the search settings page will be returned.
- *
- * @return
- * Array of hook_search_info() return values, keyed by module name. The
- * 'title' and 'path' array elements will be set to defaults for each module
- * if not supplied by hook_search_info(), and an additional array element of
- * 'module' will be added (set to the module name).
- */
- function search_get_info($all = FALSE) {
- $search_hooks = &backdrop_static(__FUNCTION__);
-
- if (!isset($search_hooks)) {
- foreach (module_implements('search_info') as $module) {
- $search_hooks[$module] = call_user_func($module . '_search_info');
-
- $search_hooks[$module] += array('title' => $module, 'path' => $module);
-
- $search_hooks[$module]['module'] = $module;
- }
- }
-
- if ($all) {
- return $search_hooks;
- }
-
-
- return array_intersect_key($search_hooks, array_flip((array) config_get('search.settings', 'search_active_modules')));
- }
-
- * Returns information about the default search module.
- *
- * @return
- * The search_get_info() array element for the default search module, if any.
- */
- function search_get_default_module_info() {
- $info = search_get_info();
- $default = config_get('search.settings', 'search_default_module');
- if (isset($info[$default])) {
- return $info[$default];
- }
-
-
- return reset($info);
- }
-
- * Access callback for search tabs.
- */
- function _search_menu_access($name) {
- return user_access('search content') && (!function_exists($name . '_search_access') || module_invoke($name, 'search_access'));
- }
-
- * Clears a part of or the entire search index.
- *
- * @param $sid
- * (optional) The ID of the item to remove from the search index. If
- * specified, $module must also be given. Omit both $sid and $module to clear
- * the entire search index.
- * @param $module
- * (optional) The machine-readable name of the module for the item to remove
- * from the search index.
- */
- function search_reindex($sid = NULL, $module = NULL, $reindex = FALSE) {
- if ($module == NULL && $sid == NULL) {
- module_invoke_all('search_reset');
- }
- else {
- db_delete('search_dataset')
- ->condition('sid', $sid)
- ->condition('type', $module)
- ->execute();
- db_delete('search_index')
- ->condition('sid', $sid)
- ->condition('type', $module)
- ->execute();
-
- if (!$reindex) {
- db_delete('search_node_links')
- ->condition('sid', $sid)
- ->condition('type', $module)
- ->execute();
- }
- }
- }
-
- * Marks a word as "dirty" (changed), or retrieves the list of dirty words.
- *
- * This is used during indexing (cron). Words that are dirty have outdated
- * total counts in the search_total table, and need to be recounted.
- */
- function search_dirty($word = NULL) {
- $dirty = &backdrop_static(__FUNCTION__, array());
- if ($word !== NULL) {
- $dirty[$word] = TRUE;
- }
- else {
- return $dirty;
- }
- }
-
- * Implements hook_cron().
- *
- * Fires hook_update_index() in all modules and cleans up dirty words.
- *
- * @see search_dirty()
- */
- function search_cron() {
-
- backdrop_register_shutdown_function('search_update_totals');
-
- $search_active_modules = config_get('search.settings', 'search_active_modules');
- if (isset($search_active_modules)) {
- foreach ($search_active_modules as $module) {
- module_invoke($module, 'update_index');
- }
- }
- }
-
- * Updates the {search_total} database table.
- *
- * This function is called on shutdown to ensure that {search_total} is always
- * up to date (even if cron times out or otherwise fails).
- */
- function search_update_totals() {
-
- foreach (search_dirty() as $word => $dummy) {
-
- $total = db_query("SELECT SUM(score) FROM {search_index} WHERE word = :word", array(':word' => $word), array('target' => 'replica'))->fetchField();
-
- $total = log10(1 + 1/(max(1, $total)));
- db_merge('search_total')
- ->key(array('word' => $word))
- ->fields(array('count' => $total))
- ->execute();
- }
-
-
-
- $result = db_query("SELECT t.word AS realword, i.word FROM {search_total} t LEFT JOIN {search_index} i ON t.word = i.word WHERE i.word IS NULL", array(), array('target' => 'replica'));
- $or = db_or();
- foreach ($result as $word) {
- $or->condition('word', $word->realword);
- }
- if (count($or) > 0) {
- db_delete('search_total')
- ->condition($or)
- ->execute();
- }
- }
-
- * Simplifies a string according to indexing rules.
- *
- * @param $text
- * Text to simplify.
- *
- * @return
- * Simplified text.
- *
- * @see hook_search_preprocess()
- */
- function search_simplify($text) {
-
- $text = decode_entities($text);
-
-
- $text = backdrop_strtolower($text);
-
-
- include_once BACKDROP_ROOT . '/core/includes/transliteration.inc';
- $text = transliteration_remove_diacritics($text);
-
-
- search_invoke_preprocess($text);
-
-
- if (config_get('search.settings', 'search_overlap_cjk')) {
- $text = preg_replace_callback('/[' . PREG_CLASS_CJK . ']+/u', 'search_expand_cjk', $text);
- }
-
-
-
-
-
-
-
- $text = preg_replace('/([' . PREG_CLASS_NUMBERS . ']+)[' . PREG_CLASS_PUNCTUATION . ']+(?=[' . PREG_CLASS_NUMBERS . '])/u', '\1', $text);
-
-
-
-
- $text = preg_replace('/[.-]{2,}/', ' ', $text);
-
-
-
- $text = preg_replace('/[._-]+/', '', $text);
-
-
-
- $text = preg_replace('/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']+/u', ' ', $text);
-
-
- $words = explode(' ', $text);
- array_walk($words, '_search_index_truncate');
- $text = implode(' ', $words);
-
- return $text;
- }
-
- * Splits CJK (Chinese, Japanese, Korean) text into tokens.
- *
- * The Search module matches exact words, where a word is defined to be a
- * sequence of characters delimited by spaces or punctuation. CJK languages are
- * written in long strings of characters, though, not split up into words. So
- * in order to allow search matching, we split up CJK text into tokens
- * consisting of consecutive, overlapping sequences of characters whose length
- * is equal to the 'minimum_word_size' variable. This tokenizing is only done if
- * the 'overlap_cjk' variable is TRUE.
- *
- * @param $matches
- * This function is a callback for preg_replace_callback(), which is called
- * from search_simplify(). So, $matches is an array of regular expression
- * matches, which means that $matches[0] contains the matched text -- a string
- * of CJK characters to tokenize.
- *
- * @return
- * Tokenized text, starting and ending with a space character.
- */
- function search_expand_cjk($matches) {
- $min = config_get('search.settings', 'search_minimum_word_size');
- $str = $matches[0];
- $length = backdrop_strlen($str);
-
- if ($length <= $min) {
- return ' ' . $str . ' ';
- }
- $tokens = ' ';
-
- $chars = array();
- for ($i = 0; $i < $length; $i++) {
-
- $current = backdrop_substr($str, 0, 1);
- $str = substr($str, strlen($current));
- $chars[] = $current;
- if ($i >= $min - 1) {
-
- $tokens .= implode('', $chars) . ' ';
-
- array_shift($chars);
- }
- }
- return $tokens;
- }
-
- * Simplifies and splits a string into tokens for indexing.
- */
- function search_index_split($text) {
- $last = &backdrop_static(__FUNCTION__);
- $lastsplit = &backdrop_static(__FUNCTION__ . ':lastsplit');
-
- if ($last == $text) {
- return $lastsplit;
- }
-
- $text = search_simplify($text);
- $words = explode(' ', $text);
-
-
- $last = $text;
- $lastsplit = $words;
-
- return $words;
- }
-
- * Helper function for array_walk in search_index_split.
- */
- function _search_index_truncate(&$text) {
- if (is_numeric($text)) {
- $text = ltrim($text, '0');
- }
- $text = truncate_utf8($text, 50);
- }
-
- * Invokes hook_search_preprocess() in modules.
- */
- function search_invoke_preprocess(&$text) {
- foreach (module_implements('search_preprocess') as $module) {
- $text = module_invoke($module, 'search_preprocess', $text);
- }
- }
-
- * Update the full-text search index for a particular item.
- *
- * @param $sid
- * An ID number identifying this particular item (e.g., node ID).
- * @param $module
- * The machine-readable name of the module that this item comes from (a module
- * that implements hook_search_info()).
- * @param $text
- * The content of this item. Must be a piece of HTML or plain text.
- *
- * @ingroup search
- */
- function search_index($sid, $module, $text) {
- $search_settings_config = config('search.settings');
- $minimum_word_size = $search_settings_config->get('search_minimum_word_size');
-
-
- global $base_url;
- $node_regexp = '@href=[\'"]?(?:' . preg_quote($base_url, '@') . '/|' . preg_quote(base_path(), '@') . ')(?:\?q=)?/?((?![a-z]+:)[^\'">]+)[\'">]@i';
-
-
-
-
- $tags = $search_settings_config->get('search_tag_weights');
-
-
-
- $text = str_replace(array('<', '>'), array(' <', '> '), $text);
- $text = strip_tags($text, '<' . implode('><', array_keys($tags)) . '>');
-
-
- $split = preg_split('/\s*<([^>]+?)>\s*/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
-
-
-
- $tag = FALSE;
- $link = FALSE;
- $score = 1;
- $accum = ' ';
- $tagstack = array();
- $tagwords = 0;
- $focus = 1;
-
- $results = array(0 => array());
-
- foreach ($split as $value) {
- if ($tag) {
-
- list($tagname) = explode(' ', $value, 2);
- $tagname = backdrop_strtolower($tagname);
-
- if ($tagname[0] == '/') {
- $tagname = substr($tagname, 1);
-
- if (!count($tagstack) || $tagstack[0] != $tagname) {
- $tagstack = array();
- $score = 1;
- }
- else {
-
- $score = max(1, $score - $tags[array_shift($tagstack)]);
- }
- if ($tagname == 'a') {
- $link = FALSE;
- }
- }
- else {
- if (isset($tagstack[0]) && $tagstack[0] == $tagname) {
-
-
- $tagstack = array();
- $score = 1;
- }
- else {
-
- array_unshift($tagstack, $tagname);
- $score += $tags[$tagname];
- }
- if ($tagname == 'a') {
-
- if (preg_match($node_regexp, $value, $match)) {
- $path = backdrop_get_normal_path($match[1]);
- if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) {
- $linknid = $match[1];
- if ($linknid > 0) {
- $node = db_query('SELECT title, nid, vid FROM {node} WHERE nid = :nid', array(':nid' => $linknid), array('target' => 'replica'))->fetchObject();
- $link = TRUE;
- $linktitle = $node->title;
- }
- }
- }
- }
- }
-
- $tagwords = 0;
- }
- else {
-
- if ($value != '') {
- if ($link) {
-
- if (preg_match('!^https?://!i', $value)) {
- $value = $linktitle;
- }
- }
- $words = search_index_split($value);
- foreach ($words as $word) {
-
- $accum .= $word . ' ';
-
- if (is_numeric($word) || backdrop_strlen($word) >= $minimum_word_size) {
-
- if ($link) {
- if (!isset($results[$linknid])) {
- $results[$linknid] = array();
- }
- $results[$linknid][] = $word;
-
- $focus *= 0.2;
- }
-
- if (!isset($results[0][$word])) {
- $results[0][$word] = 0;
- }
- $results[0][$word] += $score * $focus;
-
-
-
- $focus = min(1, .01 + 3.5 / (2 + count($results[0]) * .015));
- }
- $tagwords++;
-
- if (count($tagstack) && $tagwords >= 15) {
- $tagstack = array();
- $score = 1;
- }
- }
- }
- }
- $tag = !$tag;
- }
-
- search_reindex($sid, $module, TRUE);
-
-
- db_insert('search_dataset')
- ->fields(array(
- 'sid' => $sid,
- 'type' => $module,
- 'data' => $accum,
- 'reindex' => 0,
- ))
- ->execute();
-
-
- foreach ($results[0] as $word => $score) {
-
-
-
- db_merge('search_index')
- ->key(array(
- 'word' => $word,
- 'sid' => $sid,
- 'type' => $module,
- ))
- ->fields(array('score' => $score))
- ->expression('score', 'score + :score', array(':score' => $score))
- ->execute();
- search_dirty($word);
- }
- unset($results[0]);
-
-
- $result = db_query("SELECT nid, caption FROM {search_node_links} WHERE sid = :sid AND type = :type", array(
- ':sid' => $sid,
- ':type' => $module
- ), array('target' => 'replica'));
- $links = array();
- foreach ($result as $link) {
- $links[$link->nid] = $link->caption;
- }
-
-
- foreach ($results as $nid => $words) {
- $caption = implode(' ', $words);
- if (isset($links[$nid])) {
- if ($links[$nid] != $caption) {
-
- db_update('search_node_links')
- ->fields(array('caption' => $caption))
- ->condition('sid', $sid)
- ->condition('type', $module)
- ->condition('nid', $nid)
- ->execute();
- search_touch_node($nid);
- }
-
- unset($links[$nid]);
- }
- elseif ($sid != $nid || $module != 'node') {
-
-
- db_insert('search_node_links')
- ->fields(array(
- 'caption' => $caption,
- 'sid' => $sid,
- 'type' => $module,
- 'nid' => $nid,
- ))
- ->execute();
- search_touch_node($nid);
- }
- }
-
- foreach ($links as $nid => $caption) {
- db_delete('search_node_links')
- ->condition('sid', $sid)
- ->condition('type', $module)
- ->condition('nid', $nid)
- ->execute();
- search_touch_node($nid);
- }
- }
-
- * Changes a node's changed timestamp to 'now' to force reindexing.
- *
- * @param $nid
- * The node ID of the node that needs reindexing.
- */
- function search_touch_node($nid) {
- db_update('search_dataset')
- ->fields(array('reindex' => REQUEST_TIME))
- ->condition('type', 'node')
- ->condition('sid', $nid)
- ->execute();
- }
-
- * Implements hook_node_update_index().
- */
- function search_node_update_index(Node $node) {
-
- $result = db_query("SELECT caption FROM {search_node_links} WHERE nid = :nid", array(':nid' => $node->nid), array('target' => 'replica'));
- $output = array();
- foreach ($result as $link) {
- $output[] = $link->caption;
- }
- if (count($output)) {
- return '<a>(' . implode(', ', $output) . ')</a>';
- }
- }
-
- * Implements hook_node_update().
- */
- function search_node_update(Node $node) {
-
-
- search_touch_node($node->nid);
- }
-
- * Implements hook_comment_insert().
- */
- function search_comment_insert($comment) {
-
- search_touch_node($comment->nid);
- }
-
- * Implements hook_comment_update().
- */
- function search_comment_update($comment) {
-
- search_touch_node($comment->nid);
- }
-
- * Implements hook_comment_delete().
- */
- function search_comment_delete($comment) {
-
- search_touch_node($comment->nid);
- }
-
- * Implements hook_comment_publish().
- */
- function search_comment_publish($comment) {
-
- search_touch_node($comment->nid);
- }
-
- * Implements hook_comment_unpublish().
- */
- function search_comment_unpublish($comment) {
-
- search_touch_node($comment->nid);
- }
-
- * Extracts a module-specific search option from a search expression.
- *
- * Search options are added using search_expression_insert(), and retrieved
- * using search_expression_extract(). They take the form option:value, and
- * are added to the ordinary keywords in the search expression.
- *
- * @param $expression
- * The search expression to extract from.
- * @param $option
- * The name of the option to retrieve from the search expression.
- *
- * @return
- * The value previously stored in the search expression for option $option,
- * if any. Trailing spaces in values will not be included.
- */
- function search_expression_extract($expression, $option) {
- if (preg_match('/(^| )' . $option . ':([^ ]*)( |$)/i', $expression, $matches)) {
- return $matches[2];
- }
- }
-
- * Adds a module-specific search option to a search expression.
- *
- * Search options are added using search_expression_insert(), and retrieved
- * using search_expression_extract(). They take the form option:value, and
- * are added to the ordinary keywords in the search expression.
- *
- * @param $expression
- * The search expression to add to.
- * @param $option
- * The name of the option to add to the search expression.
- * @param $value
- * The value to add for the option. If present, it will replace any previous
- * value added for the option. Cannot contain any spaces or | characters, as
- * these are used as delimiters. If you want to add a blank value $option: to
- * the search expression, pass in an empty string or a string that is composed
- * of only spaces. To clear a previously-stored option without adding a
- * replacement, pass in NULL for $value or omit.
- *
- * @return
- * $expression, with any previous value for this option removed, and a new
- * $option:$value pair added if $value was provided.
- */
- function search_expression_insert($expression, $option, $value = NULL) {
-
- $expression = trim(preg_replace('/(^| )' . $option . ':[^ ]*/i', '', $expression));
-
-
- if (isset($value)) {
- $expression .= ' ' . $option . ':' . trim($value);
- }
- return $expression;
- }
-
- * @defgroup search Search interface
- * @{
- * The Backdrop search interface manages a global search mechanism.
- *
- * Modules may plug into this system to provide searches of different types of
- * data. Most of the system is handled by search.module, so this must be enabled
- * for all of the search features to work.
- *
- * There are three ways to interact with the search system:
- * - Specifically for searching nodes, you can implement
- * hook_node_update_index() and hook_node_search_result(). However, note that
- * the search system already indexes all visible output of a node; i.e.,
- * everything displayed normally by hook_view() and hook_node_view(). This is
- * usually sufficient. You should only use this mechanism if you want
- * additional, non-visible data to be indexed.
- * - Implement hook_search_info(). This will create a search tab for your module
- * on the /search page with a simple keyword search form. You will also need
- * to implement hook_search_execute() to perform the search.
- * - Implement hook_update_index(). This allows your module to use Backdrop's
- * HTML indexing mechanism for searching full text efficiently.
- *
- * If your module needs to provide a more complicated search form, then you need
- * to implement it yourself without hook_search_info(). In that case, you should
- * define it as a local task (tab) under the /search page (e.g.
- * /search/my_module), so that users know where to find it.
- */
-
- * Builds a search form.
- *
- * @param $action
- * Form action. Defaults to "search/$path", where $path is the search path
- * associated with the module in its hook_search_info(). This will be run
- * through url().
- * @param $keys
- * The search string entered by the user, containing keywords for the search.
- * @param $module
- * The search module to render the form for: a module that implements
- * hook_search_info(). If not supplied, the default search module is used.
- * @param $prompt
- * Label for the keywords field. Defaults to t('Enter your keywords') if NULL.
- * Supply '' to omit.
- *
- * @return
- * A Form API array for the search form.
- */
- function search_form($form, &$form_state, $action = '', $keys = '', $module = NULL, $prompt = NULL) {
- $module_info = FALSE;
- if (!$module) {
- $module_info = search_get_default_module_info();
- }
- else {
- $info = search_get_info();
- $module_info = isset($info[$module]) ? $info[$module] : FALSE;
- }
-
-
- if (!$module_info) {
- form_set_error(NULL, t('Search is currently disabled.'), 'error');
- return $form;
- }
-
- if (!$action) {
- $action = 'search/' . $module_info['path'];
- }
- if (!isset($prompt)) {
- $prompt = t('Enter your keywords');
- }
-
- $form['#action'] = url($action);
-
- $form_state['action'] = $action;
- $form['module'] = array('#type' => 'value', '#value' => $module);
- $form['basic'] = array('#type' => 'container', '#attributes' => array('class' => array('container-inline')));
-
-
-
- $form['basic']['keys'] = array(
- '#type' => 'search',
- '#title' => $prompt,
- '#default_value' => $keys,
- '#size' => $prompt ? 40 : 20,
- '#maxlength' => 255,
- );
-
-
- $form['basic']['processed_keys'] = array('#type' => 'value', '#value' => '');
- $form['basic']['actions'] = array('#type' => 'actions');
- $form['basic']['actions']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
-
- return $form;
- }
-
- * Form builder; Output a search form for the search block's search box.
- *
- * @ingroup forms
- * @see search_box_form_submit()
- */
- function search_box($form, &$form_state, $form_id) {
-
-
- $form[$form_id] = array(
- '#type' => 'search',
- '#title' => t('Search'),
- '#title_display' => 'invisible',
- '#size' => 15,
- '#default_value' => '',
- '#attributes' => array('title' => t('Enter the keywords you wish to search for.')),
- );
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
- $form['#submit'][] = 'search_box_form_submit';
-
- return $form;
- }
-
- * Process a block search form submission.
- */
- function search_box_form_submit($form, &$form_state) {
-
-
-
-
- if (isset($_GET['destination'])) {
- unset($_GET['destination']);
- }
-
-
-
-
-
- if ($form_state['values']['search_block_form'] == '') {
- form_set_error('keys', t('Please enter some keywords.'));
- }
-
- $form_id = $form['form_id']['#value'];
- $info = search_get_default_module_info();
- if ($info) {
- $form_state['redirect'] = 'search/' . $info['path'] . '/' . trim($form_state['values'][$form_id]);
- }
- else {
- form_set_error(NULL, t('Search is currently disabled.'), 'error');
- }
- }
-
- * Implements hook_form_FORM_ID_alter().
- */
- function search_form_layout_block_configure_form_alter(&$form, &$form_state) {
-
- if ($form_state['block']->module == 'search' && $form_state['block']->delta == 'form') {
-
- if ($form_state['block']->is_new == TRUE) {
- $form['title_display']['title_display']['#default_value'] = 'none';
- }
- }
- }
-
- * Performs a search by calling hook_search_execute().
- *
- * @param $keys
- * Keyword query to search on.
- * @param $module
- * Search module to search.
- * @param $conditions
- * Optional array of additional search conditions.
- *
- * @return
- * Renderable array of search results. No return value if $keys are not
- * supplied or if the given search module is not active.
- */
- function search_data($keys, $module, $conditions = NULL) {
- if (module_hook($module, 'search_execute')) {
- $results = module_invoke($module, 'search_execute', $keys, $conditions);
- if (module_hook($module, 'search_page')) {
- return module_invoke($module, 'search_page', $results);
- }
- else {
- return array(
- '#theme' => 'search_results',
- '#results' => $results,
- '#module' => $module,
- );
- }
- }
- }
-
- * Returns snippets from a piece of text, with certain keywords highlighted.
- * Used for formatting search results.
- *
- * @param $keys
- * A string containing a search query.
- *
- * @param $text
- * The text to extract fragments from.
- *
- * @return
- * A string containing HTML for the excerpt.
- */
- function search_excerpt($keys, $text) {
-
- $boundary = '(?:(?<=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . PREG_CLASS_CJK . '])|(?=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . PREG_CLASS_CJK . ']))';
-
-
- preg_match_all('/ ("([^"]+)"|(?!OR)([^" ]+))/', ' ' . $keys, $matches);
- $keys = array_merge($matches[2], $matches[3]);
-
-
- $text = strip_tags(str_replace(array('<', '>'), array(' <', '> '), $text));
- $text = decode_entities($text);
-
-
- array_walk($keys, '_search_excerpt_replace');
- $workkeys = $keys;
-
-
-
-
-
- $ranges = array();
- $included = array();
- $foundkeys = array();
- $length = 0;
- while ($length < 256 && count($workkeys)) {
- foreach ($workkeys as $k => $key) {
- if (strlen($key) == 0) {
- unset($workkeys[$k]);
- unset($keys[$k]);
- continue;
- }
- if ($length >= 256) {
- break;
- }
-
-
- if (!isset($included[$key])) {
- $included[$key] = 0;
- }
-
-
-
- $p = 0;
- if (preg_match('/' . $boundary . $key . $boundary . '/iu', $text, $match, PREG_OFFSET_CAPTURE, $included[$key])) {
- $p = $match[0][1];
- }
- else {
- $info = search_simplify_excerpt_match($key, $text, $included[$key], $boundary);
- if (!empty($info) && $info['where']) {
- $p = $info['where'];
- if ($info['keyword']) {
- $foundkeys[] = $info['keyword'];
- }
- }
- }
-
-
-
- if ($p) {
- if (($q = strpos(' ' . $text, ' ', max(0, $p - 61))) !== FALSE) {
- $end = substr($text . ' ', $p, 80);
- if (($s = strrpos($end, ' ')) !== FALSE) {
-
- $q = max($q - 1, 0);
- $s = min($s, strlen($end) - 1);
- $ranges[$q] = $p + $s;
- $length += $p + $s - $q;
- $included[$key] = $p + 1;
- }
- else {
- unset($workkeys[$k]);
- }
- }
- else {
- unset($workkeys[$k]);
- }
- }
- else {
- unset($workkeys[$k]);
- }
- }
- }
-
- if (count($ranges) == 0) {
-
-
-
- return check_plain(truncate_utf8($text, 256, TRUE, TRUE));
- }
-
-
- ksort($ranges);
-
-
- $newranges = array();
- foreach ($ranges as $from2 => $to2) {
- if (!isset($from1)) {
- $from1 = $from2;
- $to1 = $to2;
- continue;
- }
- if ($from2 <= $to1) {
- $to1 = max($to1, $to2);
- }
- else {
- $newranges[$from1] = $to1;
- $from1 = $from2;
- $to1 = $to2;
- }
- }
- $newranges[$from1] = $to1;
-
-
- $out = array();
- foreach ($newranges as $from => $to) {
- $out[] = substr($text, $from, $to - $from);
- }
-
-
- $dots = explode('!excerpt', t('... !excerpt ... !excerpt ...'));
-
- $text = (isset($newranges[0]) ? '' : $dots[0]) . implode($dots[1], $out) . $dots[2];
- $text = check_plain($text);
-
-
- array_walk($foundkeys, '_search_excerpt_replace');
- $keys = array_merge($keys, $foundkeys);
-
-
- $text = preg_replace('/' . $boundary . '(' . implode('|', $keys) . ')' . $boundary . '/iu', '<strong>\0</strong>', $text);
- return $text;
- }
-
- * @} End of "defgroup search".
- */
-
- * Helper function for array_walk() in search_excerpt().
- */
- function _search_excerpt_replace(&$text) {
- $text = preg_quote($text, '/');
- }
-
- * Find words in the original text that matched via search_simplify().
- *
- * This is called in search_excerpt() if an exact match is not found in the
- * text, so that we can find the derived form that matches.
- *
- * @param $key
- * The keyword to find.
- * @param $text
- * The text to search for the keyword.
- * @param $offset
- * Offset position in $text to start searching at.
- * @param $boundary
- * Text to include in a regular expression that will match a word boundary.
- *
- * @return
- * FALSE if no match is found. If a match is found, return an associative
- * array with element 'where' giving the position of the match, and element
- * 'keyword' giving the actual word found in the text at that position.
- */
- function search_simplify_excerpt_match($key, $text, $offset, $boundary) {
- $pos = NULL;
- $simplified_key = search_simplify($key);
- $simplified_text = search_simplify($text);
-
-
- if (!$simplified_key || !$simplified_text) {
- return FALSE;
- }
-
-
- if (!preg_match('/' . $boundary . $simplified_key . $boundary . '/iu', $simplified_text, $match, PREG_OFFSET_CAPTURE, $offset)) {
- return FALSE;
- }
-
-
-
-
- $split = array_filter(preg_split('/' . $boundary . '/iu', $text, -1, PREG_SPLIT_OFFSET_CAPTURE), '_search_excerpt_match_filter');
- foreach ($split as $value) {
-
- if ($value[1] < $offset) {
- continue;
- }
-
-
-
- $window = substr($text, $value[1], 80);
- $simplified_window = search_simplify($window);
- if (strpos($simplified_window, $simplified_key) === 0) {
-
- $pos = $value[1];
-
-
- $length = strlen($window);
- for ($i = 1; $i <= $length; $i++) {
- $keyfound = substr($text, $value[1], $i);
- if ($simplified_key == search_simplify($keyfound)) {
- break;
- }
- }
- break;
- }
- }
-
- return $pos ? array('where' => $pos, 'keyword' => $keyfound) : FALSE;
- }
-
- * Helper function for array_filter() in search_search_excerpt_match().
- */
- function _search_excerpt_match_filter($var) {
- return strlen(trim($var[0]));
- }
-
- * Implements hook_forms().
- */
- function search_forms() {
- $forms['search_block_form']= array(
- 'callback' => 'search_box',
- 'callback arguments' => array('search_block_form'),
- );
- return $forms;
- }
-
- * Implements hook_autoload_info().
- */
- function search_autoload_info() {
- return array(
- 'SearchQuery' => 'search.extender.inc',
- 'viewsSearchQuery' => 'search.extender.inc',
-
-
- 'views_handler_argument_search' => 'views/views_handler_argument_search.inc',
- 'views_handler_field_search_score' => 'views/views_handler_field_search_score.inc',
- 'views_handler_filter_search' => 'views/views_handler_filter_search.inc',
- 'views_handler_sort_search_score' => 'views/views_handler_sort_search_score.inc',
- 'views_plugin_row_search_view' => 'views/views_plugin_row_search_view.inc',
- );
- }
-
- * Implements hook_config_info().
- */
- function search_config_info() {
- $prefixes['search.settings'] = array(
- 'label' => t('Search settings'),
- 'group' => t('Configuration'),
- );
- return $prefixes;
- }
-
- * Gets stats for total and remaining items to be indexed.
- *
- * @param array $modules
- * An array of module names whose index stats we are getting.
- *
- * @return array
- * An array with the following keys:
- * - remaining: The number of remaining items to be indexed.
- * - total: The total number of indexable items.
- */
- function search_get_stats($modules = array()) {
- if (empty($module)) {
- $modules = config_get('search.settings', 'search_active_modules');
- }
- $remaining = 0;
- $total = 0;
- foreach ($modules as $module) {
- if ($status = module_invoke($module, 'search_status')) {
- $remaining += $status['remaining'];
- $total += $status['total'];
- }
- }
-
- return array(
- 'remaining' => $remaining,
- 'total' => $total,
- );
- }
-
- * Batch operation to rebuild the search index.
- *
- * @see search_reindex_confirm()
- * @see search_reindex_confirm_submit()
- */
- function search_reindex_batch($passes_needed, $search_active_modules, &$context) {
- if (!isset($context['sandbox']['current_pass'])) {
- $context['sandbox']['current_pass'] = 0;
- }
-
-
- if ($context['sandbox']['current_pass'] < $passes_needed) {
-
- backdrop_register_shutdown_function('search_update_totals');
- foreach ($search_active_modules as $module) {
- module_invoke($module, 'update_index');
- }
- $context['sandbox']['current_pass']++;
- $context['message'] = t('Now processing pass %pass of %needed', array('%pass' => $context['sandbox']['current_pass'], '%needed' => $passes_needed));
- $context['finished'] = $context['sandbox']['current_pass'] / $passes_needed;
- }
- }
-
- * Batch 'finished' callback for rebuilding the search index.
- *
- * @see search_reindex_batch()
- */
- function search_reindex_batch_finished($success, $results, $operations) {
- if ($success) {
- $config = config('search.settings');
-
- $remaining = 0;
- $total = 0;
- foreach ($config->get('search_active_modules') as $module) {
- if ($status = module_invoke($module, 'search_status')) {
- $remaining += $status['remaining'];
- $total += $status['total'];
- }
- }
- $count = format_plural($remaining, 'There is 1 item left to index.', 'There are @count items left to index.');
- $percentage = ((int)min(100, 100 * ($total - $remaining) / max(1, $total))) . '%';
- $message = '<p><strong>' . t('%percentage of the site has been indexed.', array('%percentage' => $percentage)) . ' ' . $count . '</strong></p>';
- backdrop_set_message($message);
- }
- else {
-
- $error_operation = reset($operations);
- backdrop_set_message(
- t('An error occurred while processing @operation with arguments : @args',
- array(
- '@operation' => $error_operation[0],
- '@args' => print_r($error_operation[0], TRUE),
- )
- )
- );
- }
- }