- <?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),
-         )
-       )
-     );
-   }
- }