includes/search.php
changeset 1227 bdac73ed481e
parent 1201 9593e62929d1
child 1228 9e9334417dbc
equal deleted inserted replaced
1226:de56132c008d 1227:bdac73ed481e
    23  */
    23  */
    24 
    24 
    25 class Searcher
    25 class Searcher
    26 {
    26 {
    27 
    27 
    28   var $results;
    28 	var $results;
    29   var $index;
    29 	var $index;
    30   var $warnings;
    30 	var $warnings;
    31   var $match_case = false;
    31 	var $match_case = false;
    32 
    32 
    33   function buildIndex($texts)
    33 	function buildIndex($texts)
    34   {
    34 	{
    35     $this->index = Array();
    35 		$this->index = Array();
    36     $stopwords = get_stopwords();
    36 		$stopwords = get_stopwords();
    37 
    37 
    38     foreach($texts as $i => $l)
    38 		foreach($texts as $i => $l)
    39     {
    39 		{
    40       $seed = md5(microtime(true) . mt_rand());
    40 			$seed = md5(microtime(true) . mt_rand());
    41       $texts[$i] = str_replace("'", 'xxxApoS'.$seed.'xxx', $texts[$i]);
    41 			$texts[$i] = str_replace("'", 'xxxApoS'.$seed.'xxx', $texts[$i]);
    42       $texts[$i] = preg_replace('#([\W_]+)#i', ' ', $texts[$i]);
    42 			$texts[$i] = preg_replace('#([\W_]+)#i', ' ', $texts[$i]);
    43       $texts[$i] = preg_replace('#([ ]+?)#', ' ', $texts[$i]);
    43 			$texts[$i] = preg_replace('#([ ]+?)#', ' ', $texts[$i]);
    44       $texts[$i] = preg_replace('#([\']*){2,}#s', '', $texts[$i]);
    44 			$texts[$i] = preg_replace('#([\']*){2,}#s', '', $texts[$i]);
    45       $texts[$i] = str_replace('xxxApoS'.$seed.'xxx', "'", $texts[$i]);
    45 			$texts[$i] = str_replace('xxxApoS'.$seed.'xxx', "'", $texts[$i]);
    46       $l = $texts[$i];
    46 			$l = $texts[$i];
    47       $words = Array();
    47 			$words = Array();
    48       $good_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\' ';
    48 			$good_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\' ';
    49       $good_chars = enano_str_split($good_chars, 1);
    49 			$good_chars = enano_str_split($good_chars, 1);
    50       $letters = enano_str_split($l, 1);
    50 			$letters = enano_str_split($l, 1);
    51       foreach($letters as $x => $t)
    51 			foreach($letters as $x => $t)
    52       {
    52 			{
    53         if(!in_array($t, $good_chars))
    53 				if(!in_array($t, $good_chars))
    54           unset($letters[$x]);
    54 					unset($letters[$x]);
    55       }
    55 			}
    56       $letters = implode('', $letters);
    56 			$letters = implode('', $letters);
    57       $words = explode(' ', $letters);
    57 			$words = explode(' ', $letters);
    58       foreach($words as $c => $w)
    58 			foreach($words as $c => $w)
    59       {
    59 			{
    60         if(strlen($w) < 2 || in_array($w, $stopwords) || strlen($w) > 63 || preg_match('/[\']{2,}/', $w))
    60 				if(strlen($w) < 2 || in_array($w, $stopwords) || strlen($w) > 63 || preg_match('/[\']{2,}/', $w))
    61           unset($words[$c]);
    61 					unset($words[$c]);
    62         else
    62 				else
    63           $words[$c] = $w;
    63 					$words[$c] = $w;
    64       }
    64 			}
    65       $words = array_values($words);
    65 			$words = array_values($words);
    66       foreach($words as $c => $w)
    66 			foreach($words as $c => $w)
    67       {
    67 			{
    68         if(isset($this->index[$w]))
    68 				if(isset($this->index[$w]))
    69         {
    69 				{
    70           if(!in_array($i, $this->index[$w]))
    70 					if(!in_array($i, $this->index[$w]))
    71             $this->index[$w][] = $i;
    71 						$this->index[$w][] = $i;
    72         }
    72 				}
    73         else
    73 				else
    74         {
    74 				{
    75           $this->index[$w] = Array();
    75 					$this->index[$w] = Array();
    76           $this->index[$w][] = $i;
    76 					$this->index[$w][] = $i;
    77         }
    77 				}
    78       }
    78 			}
    79     }
    79 		}
    80     foreach($this->index as $k => $v)
    80 		foreach($this->index as $k => $v)
    81     {
    81 		{
    82       $this->index[$k] = implode(',', $this->index[$k]);
    82 			$this->index[$k] = implode(',', $this->index[$k]);
    83     }
    83 		}
    84   }
    84 	}
    85 }
    85 }
    86 
    86 
    87 /**
    87 /**
    88  * Searches the site for the specified string and returns an array with each value being an array filled with the following:
    88  * Searches the site for the specified string and returns an array with each value being an array filled with the following:
    89  *   page_id: string, self-explanatory
    89  *   page_id: string, self-explanatory
    98  * @return array
    98  * @return array
    99  */
    99  */
   100 
   100 
   101 function perform_search($query, &$warnings, $case_sensitive = false, &$word_list)
   101 function perform_search($query, &$warnings, $case_sensitive = false, &$word_list)
   102 {
   102 {
   103   global $db, $session, $paths, $template, $plugins; // Common objects
   103 	global $db, $session, $paths, $template, $plugins; // Common objects
   104   global $lang;
   104 	global $lang;
   105   
   105 	
   106   $warnings = array();
   106 	$warnings = array();
   107   
   107 	
   108   //
   108 	//
   109   // STAGE 0: PARSE SEARCH QUERY
   109 	// STAGE 0: PARSE SEARCH QUERY
   110   // Identify all terms of the query. Separate between what is required and what is not, and what should be sent through the index as
   110 	// Identify all terms of the query. Separate between what is required and what is not, and what should be sent through the index as
   111   // opposed to straight-out LIKE-selected.
   111 	// opposed to straight-out LIKE-selected.
   112   //
   112 	//
   113 
   113 
   114   $query = parse_search_query($query, $warnings);
   114 	$query = parse_search_query($query, $warnings);
   115 
   115 
   116   // Segregate search terms containing spaces
   116 	// Segregate search terms containing spaces
   117   $query_phrase = array(
   117 	$query_phrase = array(
   118     'any' => array(),
   118 		'any' => array(),
   119     'req' => array()
   119 		'req' => array()
   120     );
   120 		);
   121 
   121 
   122   foreach ( $query['any'] as $i => $_ )
   122 	foreach ( $query['any'] as $i => $_ )
   123   {
   123 	{
   124     $term =& $query['any'][$i];
   124 		$term =& $query['any'][$i];
   125     $term = trim($term);
   125 		$term = trim($term);
   126     // the indexer only indexes words a-z with apostrophes
   126 		// the indexer only indexes words a-z with apostrophes
   127     if ( preg_match('/[^A-Za-z\']/', $term) )
   127 		if ( preg_match('/[^A-Za-z\']/', $term) )
   128     {
   128 		{
   129       $query_phrase['any'][] = $term;
   129 			$query_phrase['any'][] = $term;
   130       unset($term, $query['any'][$i]);
   130 			unset($term, $query['any'][$i]);
   131     }
   131 		}
   132   }
   132 	}
   133   unset($term);
   133 	unset($term);
   134   $query['any'] = array_values($query['any']);
   134 	$query['any'] = array_values($query['any']);
   135 
   135 
   136   foreach ( $query['req'] as $i => $_ )
   136 	foreach ( $query['req'] as $i => $_ )
   137   {
   137 	{
   138     $term =& $query['req'][$i];
   138 		$term =& $query['req'][$i];
   139     $term = trim($term);
   139 		$term = trim($term);
   140     if ( preg_match('/[^A-Za-z\']/', $term) )
   140 		if ( preg_match('/[^A-Za-z\']/', $term) )
   141     {
   141 		{
   142       $query_phrase['req'][] = $term;
   142 			$query_phrase['req'][] = $term;
   143       unset($term, $query['req'][$i]);
   143 			unset($term, $query['req'][$i]);
   144     }
   144 		}
   145   }
   145 	}
   146   unset($term);
   146 	unset($term);
   147   $query['req'] = array_values($query['req']);
   147 	$query['req'] = array_values($query['req']);
   148 
   148 
   149   $results = array();
   149 	$results = array();
   150   $scores = array();
   150 	$scores = array();
   151   $ns_list = '(' . implode('|', array_keys($paths->nslist)) . ')';
   151 	$ns_list = '(' . implode('|', array_keys($paths->nslist)) . ')';
   152 
   152 
   153   // FIXME: Update to use FULLTEXT algo when available.
   153 	// FIXME: Update to use FULLTEXT algo when available.
   154 
   154 
   155   // Build an SQL query to load from the index table
   155 	// Build an SQL query to load from the index table
   156   if ( count($query['any']) < 1 && count($query['req']) < 1 && count($query_phrase['any']) < 1 && count($query_phrase['req']) < 1 )
   156 	if ( count($query['any']) < 1 && count($query['req']) < 1 && count($query_phrase['any']) < 1 && count($query_phrase['req']) < 1 )
   157   {
   157 	{
   158     // This is both because of technical restrictions and devastation that would occur on shared servers/large sites.
   158 		// This is both because of technical restrictions and devastation that would occur on shared servers/large sites.
   159     $warnings[] = $lang->get('search_err_query_no_positive');
   159 		$warnings[] = $lang->get('search_err_query_no_positive');
   160     return array();
   160 		return array();
   161   }
   161 	}
   162 
   162 
   163   //
   163 	//
   164   // STAGE 1
   164 	// STAGE 1
   165   // Get all possible result pages from the search index. Tally which pages have the most words, and later sort them by boolean relevance
   165 	// Get all possible result pages from the search index. Tally which pages have the most words, and later sort them by boolean relevance
   166   //
   166 	//
   167 
   167 
   168   // Skip this if no indexable words are included
   168 	// Skip this if no indexable words are included
   169 
   169 
   170   if ( count($query['any']) > 0 || count($query['req']) > 0 )
   170 	if ( count($query['any']) > 0 || count($query['req']) > 0 )
   171   {
   171 	{
   172     $where_any = array();
   172 		$where_any = array();
   173     foreach ( $query['any'] as $term )
   173 		foreach ( $query['any'] as $term )
   174     {
   174 		{
   175       $term = escape_string_like($term);
   175 			$term = escape_string_like($term);
   176       if ( !$case_sensitive )
   176 			if ( !$case_sensitive )
   177         $term = strtolower($term);
   177 				$term = strtolower($term);
   178       $where_any[] = $term;
   178 			$where_any[] = $term;
   179     }
   179 		}
   180     foreach ( $query['req'] as $term )
   180 		foreach ( $query['req'] as $term )
   181     {
   181 		{
   182       $term = escape_string_like($term);
   182 			$term = escape_string_like($term);
   183       if ( !$case_sensitive )
   183 			if ( !$case_sensitive )
   184         $term = strtolower($term);
   184 				$term = strtolower($term);
   185       $where_any[] = $term;
   185 			$where_any[] = $term;
   186     }
   186 		}
   187 
   187 
   188     $col_word = ( $case_sensitive ) ? 'word' : 'word_lcase';
   188 		$col_word = ( $case_sensitive ) ? 'word' : 'word_lcase';
   189     $where_any_str = ( count($where_any) > 0 ) ? '( ' . $col_word . ' LIKE \'%' . implode('%\' OR ' . $col_word . ' LIKE \'%', $where_any) . '%\' )' : '';
   189 		$where_any_str = ( count($where_any) > 0 ) ? '( ' . $col_word . ' LIKE \'%' . implode('%\' OR ' . $col_word . ' LIKE \'%', $where_any) . '%\' )' : '';
   190 
   190 
   191     // generate query
   191 		// generate query
   192     $sql = "SELECT word, page_names FROM " . table_prefix . "search_index WHERE {$where_any_str}";
   192 		$sql = "SELECT word, page_names FROM " . table_prefix . "search_index WHERE {$where_any_str}";
   193     if ( !($q = $db->sql_query($sql)) )
   193 		if ( !($q = $db->sql_query($sql)) )
   194       $db->_die('Error is in perform_search(), includes/search.php, query 1');
   194 			$db->_die('Error is in perform_search(), includes/search.php, query 1');
   195 
   195 
   196     $word_tracking = array();
   196 		$word_tracking = array();
   197     if ( $row = $db->fetchrow($q) )
   197 		if ( $row = $db->fetchrow($q) )
   198     {
   198 		{
   199       do
   199 			do
   200       {
   200 			{
   201         // get page list
   201 				// get page list
   202         $pages =& $row['page_names'];
   202 				$pages =& $row['page_names'];
   203           
   203 					
   204         // Find page IDs that contain commas
   204 				// Find page IDs that contain commas
   205         // This should never happen because commas are escaped by sanitize_page_id(). Nevertheless for compatibility with older
   205 				// This should never happen because commas are escaped by sanitize_page_id(). Nevertheless for compatibility with older
   206         // databases, and to alleviate the concerns of hackers, we'll accommodate for page IDs with commas here by checking for
   206 				// databases, and to alleviate the concerns of hackers, we'll accommodate for page IDs with commas here by checking for
   207         // IDs that don't match the pattern for stringified page ID + namespace. If it doesn't match, that means it's a continuation
   207 				// IDs that don't match the pattern for stringified page ID + namespace. If it doesn't match, that means it's a continuation
   208         // of the previous ID and should be concatenated to the previous entry.
   208 				// of the previous ID and should be concatenated to the previous entry.
   209         $matches = strpos($pages, ',') ? explode(',', $pages) : array($pages);
   209 				$matches = strpos($pages, ',') ? explode(',', $pages) : array($pages);
   210         $prev = false;
   210 				$prev = false;
   211         foreach ( $matches as $i => $_ )
   211 				foreach ( $matches as $i => $_ )
   212         {
   212 				{
   213           $match =& $matches[$i];
   213 					$match =& $matches[$i];
   214           if ( !preg_match("/^ns=$ns_list;pid=(.+)$/", $match) && $prev )
   214 					if ( !preg_match("/^ns=$ns_list;pid=(.+)$/", $match) && $prev )
   215           {
   215 					{
   216             $matches[$prev] .= ',' . $match;
   216 						$matches[$prev] .= ',' . $match;
   217             unset($match, $matches[$i]);
   217 						unset($match, $matches[$i]);
   218             continue;
   218 						continue;
   219           }
   219 					}
   220           $prev = $i;
   220 					$prev = $i;
   221         }
   221 				}
   222         unset($match);
   222 				unset($match);
   223 
   223 
   224         // Iterate through each of the results, assigning scores based on how many times the page has shown up.
   224 				// Iterate through each of the results, assigning scores based on how many times the page has shown up.
   225         // This works because this phase of the search is strongly word-based not page-based. If a page shows up
   225 				// This works because this phase of the search is strongly word-based not page-based. If a page shows up
   226         // multiple times while fetching the result rows from the search_index table, it simply means that page
   226 				// multiple times while fetching the result rows from the search_index table, it simply means that page
   227         // contains more than one of the terms the user searched for.
   227 				// contains more than one of the terms the user searched for.
   228 
   228 
   229         foreach ( $matches as $match )
   229 				foreach ( $matches as $match )
   230         {
   230 				{
   231           $word_cs = (( $case_sensitive ) ? $row['word'] : strtolower($row['word']));
   231 					$word_cs = (( $case_sensitive ) ? $row['word'] : strtolower($row['word']));
   232           if ( isset($word_tracking[$match]) && in_array($word_cs, $word_tracking[$match]) )
   232 					if ( isset($word_tracking[$match]) && in_array($word_cs, $word_tracking[$match]) )
   233           {
   233 					{
   234             continue;
   234 						continue;
   235           }
   235 					}
   236           if ( isset($word_tracking[$match]) )
   236 					if ( isset($word_tracking[$match]) )
   237           {
   237 					{
   238             if ( isset($word_tracking[$match]) )
   238 						if ( isset($word_tracking[$match]) )
   239             {
   239 						{
   240               $word_tracking[$match][] = $word_cs;
   240 							$word_tracking[$match][] = $word_cs;
   241             }
   241 						}
   242           }
   242 					}
   243           else
   243 					else
   244           {
   244 					{
   245             $word_tracking[$match] = array($word_cs);
   245 						$word_tracking[$match] = array($word_cs);
   246           }
   246 					}
   247           
   247 					
   248           // echo '<pre>' . print_r($word_tracking, true) . '</pre>';
   248 					// echo '<pre>' . print_r($word_tracking, true) . '</pre>';
   249           
   249 					
   250           $inc = 1;
   250 					$inc = 1;
   251 
   251 
   252           // Is this search term present in the page's title? If so, give extra points
   252 					// Is this search term present in the page's title? If so, give extra points
   253           preg_match("/^ns=$ns_list;pid=(.+)$/", $match, $piecesparts);
   253 					preg_match("/^ns=$ns_list;pid=(.+)$/", $match, $piecesparts);
   254           $title = get_page_title_ns($piecesparts[2], $piecesparts[1]);
   254 					$title = get_page_title_ns($piecesparts[2], $piecesparts[1]);
   255           
   255 					
   256           $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
   256 					$test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
   257           if ( $test_func($title, $row['word']) || $test_func($piecesparts[2], $row['word']) )
   257 					if ( $test_func($title, $row['word']) || $test_func($piecesparts[2], $row['word']) )
   258           {
   258 					{
   259             $inc = 1.5;
   259 						$inc = 1.5;
   260           }
   260 					}
   261           
   261 					
   262           // increase points if 2 or more words match a phrase in the title
   262 					// increase points if 2 or more words match a phrase in the title
   263           for ( $i = 0; $i < count($where_any) - 1; $i++ )
   263 					for ( $i = 0; $i < count($where_any) - 1; $i++ )
   264           {
   264 					{
   265             $phrase = "{$where_any[$i]} {$where_any[$i + 1]}";
   265 						$phrase = "{$where_any[$i]} {$where_any[$i + 1]}";
   266             if ( $test_func($title, $phrase) )
   266 						if ( $test_func($title, $phrase) )
   267             {
   267 						{
   268               $inc *= 1.25;
   268 							$inc *= 1.25;
   269             }
   269 						}
   270           }
   270 					}
   271           
   271 					
   272           // Deduct points if there are few similarities between the words
   272 					// Deduct points if there are few similarities between the words
   273           $lev_array = array();
   273 					$lev_array = array();
   274           foreach ( $where_any as $qword )
   274 					foreach ( $where_any as $qword )
   275           {
   275 					{
   276             if ( strstr($word_cs, $qword) )
   276 						if ( strstr($word_cs, $qword) )
   277               $lev_array[ $qword ] = levenshtein($qword, $word_cs);
   277 							$lev_array[ $qword ] = levenshtein($qword, $word_cs);
   278           }
   278 					}
   279           if ( min($lev_array) > 3 )
   279 					if ( min($lev_array) > 3 )
   280           {
   280 					{
   281             $inc /= array_sum($lev_array) / count($lev_array);
   281 						$inc /= array_sum($lev_array) / count($lev_array);
   282           }
   282 					}
   283           
   283 					
   284           if ( isset($scores[$match]) )
   284 					if ( isset($scores[$match]) )
   285           {
   285 					{
   286             $scores[$match] = $scores[$match] + $inc;
   286 						$scores[$match] = $scores[$match] + $inc;
   287           }
   287 					}
   288           else
   288 					else
   289           {
   289 					{
   290             $scores[$match] = $inc;
   290 						$scores[$match] = $inc;
   291           }
   291 					}
   292         }
   292 				}
   293       }
   293 			}
   294       while ( $row = $db->fetchrow($q) );
   294 			while ( $row = $db->fetchrow($q) );
   295     }
   295 		}
   296     $db->free_result($q);
   296 		$db->free_result($q);
   297     
   297 		
   298     //
   298 		//
   299     // STAGE 2: FIRST ELIMINATION ROUND
   299 		// STAGE 2: FIRST ELIMINATION ROUND
   300     // Iterate through the list of required terms. If a given page is not found to have the required term, eliminate it
   300 		// Iterate through the list of required terms. If a given page is not found to have the required term, eliminate it
   301     //
   301 		//
   302 
   302 
   303     foreach ( $query['req'] as $term )
   303 		foreach ( $query['req'] as $term )
   304     {
   304 		{
   305       foreach ( $word_tracking as $i => $page )
   305 			foreach ( $word_tracking as $i => $page )
   306       {
   306 			{
   307         if ( !in_array($term, $page) )
   307 				if ( !in_array($term, $page) )
   308         {
   308 				{
   309           unset($word_tracking[$i], $scores[$i]);
   309 					unset($word_tracking[$i], $scores[$i]);
   310         }
   310 				}
   311       }
   311 			}
   312     }
   312 		}
   313   }
   313 	}
   314   
   314 	
   315   //
   315 	//
   316   // STAGE 3: PHRASE SEARCHING
   316 	// STAGE 3: PHRASE SEARCHING
   317   // Use LIKE to find pages with specified phrases. We can do a super-picky single query without another elimination round because
   317 	// Use LIKE to find pages with specified phrases. We can do a super-picky single query without another elimination round because
   318   // at this stage we can search the full page_text column instead of relying on a word list.
   318 	// at this stage we can search the full page_text column instead of relying on a word list.
   319   //
   319 	//
   320 
   320 
   321   // We can skip this stage if none of these special terms apply
   321 	// We can skip this stage if none of these special terms apply
   322 
   322 
   323   $text_col = ( $case_sensitive ) ? 'page_text' : ENANO_SQLFUNC_LOWERCASE . '(page_text)';
   323 	$text_col = ( $case_sensitive ) ? 'page_text' : ENANO_SQLFUNC_LOWERCASE . '(page_text)';
   324   $name_col = ( $case_sensitive ) ? 'name' : ENANO_SQLFUNC_LOWERCASE . '(name)';
   324 	$name_col = ( $case_sensitive ) ? 'name' : ENANO_SQLFUNC_LOWERCASE . '(name)';
   325   $text_col_join = ( $case_sensitive ) ? 't.page_text' : ENANO_SQLFUNC_LOWERCASE . '(t.page_text)';
   325 	$text_col_join = ( $case_sensitive ) ? 't.page_text' : ENANO_SQLFUNC_LOWERCASE . '(t.page_text)';
   326   $name_col_join = ( $case_sensitive ) ? 'p.name' : ENANO_SQLFUNC_LOWERCASE . '(p.name)';
   326 	$name_col_join = ( $case_sensitive ) ? 'p.name' : ENANO_SQLFUNC_LOWERCASE . '(p.name)';
   327     
   327 		
   328   $concat_column = ( ENANO_DBLAYER == 'MYSQL' ) ?
   328 	$concat_column = ( ENANO_DBLAYER == 'MYSQL' ) ?
   329     'CONCAT(\'ns=\',t.namespace,\';pid=\',t.page_id)' :
   329 		'CONCAT(\'ns=\',t.namespace,\';pid=\',t.page_id)' :
   330     "'ns=' || t.namespace || ';pid=' || t.page_id";
   330 		"'ns=' || t.namespace || ';pid=' || t.page_id";
   331 
   331 
   332   if ( count($query_phrase['any']) > 0 || count($query_phrase['req']) > 0 )
   332 	if ( count($query_phrase['any']) > 0 || count($query_phrase['req']) > 0 )
   333   {
   333 	{
   334 
   334 
   335     $where_any = array();
   335 		$where_any = array();
   336     foreach ( $query_phrase['any'] as $term )
   336 		foreach ( $query_phrase['any'] as $term )
   337     {
   337 		{
   338       $term = escape_string_like($term);
   338 			$term = escape_string_like($term);
   339       if ( !$case_sensitive )
   339 			if ( !$case_sensitive )
   340         $term = strtolower($term);
   340 				$term = strtolower($term);
   341       $where_any[] = "( $text_col LIKE '%$term%' OR $name_col LIKE '%$term%' )";
   341 			$where_any[] = "( $text_col LIKE '%$term%' OR $name_col LIKE '%$term%' )";
   342     }
   342 		}
   343 
   343 
   344     $where_any = ( count($where_any) > 0 ) ? implode(" OR\n  ", $where_any) : '';
   344 		$where_any = ( count($where_any) > 0 ) ? implode(" OR\n  ", $where_any) : '';
   345 
   345 
   346     // Also do required terms, but use AND to ensure that all required terms are included
   346 		// Also do required terms, but use AND to ensure that all required terms are included
   347     $where_req = array();
   347 		$where_req = array();
   348     foreach ( $query_phrase['req'] as $term )
   348 		foreach ( $query_phrase['req'] as $term )
   349     {
   349 		{
   350       $term = escape_string_like($term);
   350 			$term = escape_string_like($term);
   351       if ( !$case_sensitive )
   351 			if ( !$case_sensitive )
   352         $term = strtolower($term);
   352 				$term = strtolower($term);
   353       $where_req[] = "( $text_col LIKE '%$term%' OR $name_col LIKE '%$term%' )";
   353 			$where_req[] = "( $text_col LIKE '%$term%' OR $name_col LIKE '%$term%' )";
   354     }
   354 		}
   355     $and_clause = ( $where_any != '' ) ? 'AND ' : '';
   355 		$and_clause = ( $where_any != '' ) ? 'AND ' : '';
   356     $where_req = ( count($where_req) > 0 ) ? "{$and_clause}" . implode(" AND\n  ", $where_req) : '';
   356 		$where_req = ( count($where_req) > 0 ) ? "{$and_clause}" . implode(" AND\n  ", $where_req) : '';
   357 
   357 
   358     $sql = 'SELECT ' . $concat_column . ' AS id, p.name, t.page_text FROM ' . table_prefix . "page_text AS t\n"
   358 		$sql = 'SELECT ' . $concat_column . ' AS id, p.name, t.page_text FROM ' . table_prefix . "page_text AS t\n"
   359             . "  LEFT JOIN " . table_prefix . "pages AS p\n"
   359 						. "  LEFT JOIN " . table_prefix . "pages AS p\n"
   360             . "    ON ( p.urlname = t.page_id AND p.namespace = t.namespace )\n"
   360 						. "    ON ( p.urlname = t.page_id AND p.namespace = t.namespace )\n"
   361             . "  WHERE p.visible = 1 AND (\n    $where_any\n    $where_req\n  );";
   361 						. "  WHERE p.visible = 1 AND (\n    $where_any\n    $where_req\n  );";
   362     if ( !($q = $db->sql_query($sql)) )
   362 		if ( !($q = $db->sql_query($sql)) )
   363       $db->_die('Error is in perform_search(), includes/search.php, query 2. Parsed query dump follows:<pre>(indexable) ' . htmlspecialchars(print_r($query, true)) . '(non-indexable) ' . htmlspecialchars(print_r($query_phrase, true)) . '</pre>');
   363 			$db->_die('Error is in perform_search(), includes/search.php, query 2. Parsed query dump follows:<pre>(indexable) ' . htmlspecialchars(print_r($query, true)) . '(non-indexable) ' . htmlspecialchars(print_r($query_phrase, true)) . '</pre>');
   364 
   364 
   365     if ( $row = $db->fetchrow() )
   365 		if ( $row = $db->fetchrow() )
   366     {
   366 		{
   367       do
   367 			do
   368       {
   368 			{
   369         $id =& $row['id'];
   369 				$id =& $row['id'];
   370         $inc = 0.0;
   370 				$inc = 0.0;
   371 
   371 
   372         $title = $row['name'];
   372 				$title = $row['name'];
   373         $test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
   373 				$test_func = ( $case_sensitive ) ? 'strstr' : 'stristr';
   374         
   374 				
   375         // Is this search term present in the page's title? If so, give extra points
   375 				// Is this search term present in the page's title? If so, give extra points
   376         $word_list = array_merge($query_phrase['any'], $query_phrase['req']);
   376 				$word_list = array_merge($query_phrase['any'], $query_phrase['req']);
   377         foreach ( $word_list as $word )
   377 				foreach ( $word_list as $word )
   378         {
   378 				{
   379           if ( $test_func($title, $word) )
   379 					if ( $test_func($title, $word) )
   380             $inc += 1.5;
   380 						$inc += 1.5;
   381           else if ( $test_func($row['page_text'], $word) )
   381 					else if ( $test_func($row['page_text'], $word) )
   382             $inc += 1.0;
   382 						$inc += 1.0;
   383         }
   383 				}
   384         
   384 				
   385         // increase points if 2 or more words match a phrase in the title
   385 				// increase points if 2 or more words match a phrase in the title
   386         for ( $i = 0; $i < count($word_list) - 1; $i++ )
   386 				for ( $i = 0; $i < count($word_list) - 1; $i++ )
   387         {
   387 				{
   388           $phrase = "{$word_list[$i]} {$word_list[$i + 1]}";
   388 					$phrase = "{$word_list[$i]} {$word_list[$i + 1]}";
   389           if ( $test_func($title, $phrase) )
   389 					if ( $test_func($title, $phrase) )
   390             $inc *= 1.25;
   390 						$inc *= 1.25;
   391           else if ( $test_func($row['page_text'], $phrase) )
   391 					else if ( $test_func($row['page_text'], $phrase) )
   392             $inc *= 1.125;
   392 						$inc *= 1.125;
   393         }
   393 				}
   394         
   394 				
   395         if ( isset($scores[$id]) )
   395 				if ( isset($scores[$id]) )
   396         {
   396 				{
   397           $scores[$id] = $scores[$id] + $inc;
   397 					$scores[$id] = $scores[$id] + $inc;
   398         }
   398 				}
   399         else
   399 				else
   400         {
   400 				{
   401           $scores[$id] = $inc;
   401 					$scores[$id] = $inc;
   402         }
   402 				}
   403       }
   403 			}
   404       while ( $row = $db->fetchrow() );
   404 			while ( $row = $db->fetchrow() );
   405     }
   405 		}
   406     $db->free_result();
   406 		$db->free_result();
   407   }
   407 	}
   408 
   408 
   409   //
   409 	//
   410   // STAGE 4 - SELECT PAGE TEXT AND ELIMINATE NOTS
   410 	// STAGE 4 - SELECT PAGE TEXT AND ELIMINATE NOTS
   411   // At this point, we have a complete list of all the possible pages. Now we want to obtain the page text, and within the same query
   411 	// At this point, we have a complete list of all the possible pages. Now we want to obtain the page text, and within the same query
   412   // eliminate any terms that shouldn't be in there.
   412 	// eliminate any terms that shouldn't be in there.
   413   //
   413 	//
   414 
   414 
   415   // Generate master word list for the highlighter
   415 	// Generate master word list for the highlighter
   416   $word_list = array_values(array_merge($query['any'], $query['req'], $query_phrase['any'], $query_phrase['req']));
   416 	$word_list = array_values(array_merge($query['any'], $query['req'], $query_phrase['any'], $query_phrase['req']));
   417 
   417 
   418   $text_where = array();
   418 	$text_where = array();
   419   foreach ( $scores as $page_id => $_ )
   419 	foreach ( $scores as $page_id => $_ )
   420   {
   420 	{
   421     $text_where[] = $db->escape($page_id);
   421 		$text_where[] = $db->escape($page_id);
   422   }
   422 	}
   423   $text_where = '( ' . $concat_column . ' = \'' . implode('\' OR ' . $concat_column . ' = \'', $text_where) . '\' )';
   423 	$text_where = '( ' . $concat_column . ' = \'' . implode('\' OR ' . $concat_column . ' = \'', $text_where) . '\' )';
   424 
   424 
   425   if ( count($query['not']) > 0 )
   425 	if ( count($query['not']) > 0 )
   426     $text_where .= ' AND';
   426 		$text_where .= ' AND';
   427 
   427 
   428   $where_not = array();
   428 	$where_not = array();
   429   foreach ( $query['not'] as $term )
   429 	foreach ( $query['not'] as $term )
   430   {
   430 	{
   431     $term = escape_string_like($term);
   431 		$term = escape_string_like($term);
   432     if ( !$case_sensitive )
   432 		if ( !$case_sensitive )
   433       $term = strtolower($term);
   433 			$term = strtolower($term);
   434     $where_not[] = $term;
   434 		$where_not[] = $term;
   435   }
   435 	}
   436   $where_not = ( count($where_not) > 0 ) ? "$text_col NOT LIKE '%" . implode("%' AND $text_col NOT LIKE '%", $where_not) . "%'" : '';
   436 	$where_not = ( count($where_not) > 0 ) ? "$text_col NOT LIKE '%" . implode("%' AND $text_col NOT LIKE '%", $where_not) . "%'" : '';
   437 
   437 
   438   $sql = 'SELECT ' . $concat_column . ' AS id, t.page_id, t.namespace, CHAR_LENGTH(t.page_text) AS page_length, t.page_text, p.name AS page_name FROM ' . table_prefix . "page_text AS t
   438 	$sql = 'SELECT ' . $concat_column . ' AS id, t.page_id, t.namespace, CHAR_LENGTH(t.page_text) AS page_length, t.page_text, p.name AS page_name FROM ' . table_prefix . "page_text AS t
   439             LEFT JOIN " . table_prefix . "pages AS p
   439 						LEFT JOIN " . table_prefix . "pages AS p
   440               ON ( p.urlname = t.page_id AND p.namespace = t.namespace )
   440 							ON ( p.urlname = t.page_id AND p.namespace = t.namespace )
   441             WHERE p.visible = 1 AND ( $text_where $where_not );";
   441 						WHERE p.visible = 1 AND ( $text_where $where_not );";
   442   if ( !($q = $db->sql_unbuffered_query($sql)) )
   442 	if ( !($q = $db->sql_unbuffered_query($sql)) )
   443     $db->_die('Error is in perform_search(), includes/search.php, query 3');
   443 		$db->_die('Error is in perform_search(), includes/search.php, query 3');
   444 
   444 
   445   $page_data = array();
   445 	$page_data = array();
   446   if ( $row = $db->fetchrow() )
   446 	if ( $row = $db->fetchrow() )
   447   {
   447 	{
   448     do
   448 		do
   449     {
   449 		{
   450       $row['page_text'] = htmlspecialchars($row['page_text']);
   450 			$row['page_text'] = htmlspecialchars($row['page_text']);
   451       $row['page_name'] = htmlspecialchars($row['page_name']);
   451 			$row['page_name'] = htmlspecialchars($row['page_name']);
   452 
   452 
   453       // Highlight results (this is wonderfully automated)
   453 			// Highlight results (this is wonderfully automated)
   454       $row['page_text'] = highlight_and_clip_search_result($row['page_text'], $word_list, $case_sensitive);
   454 			$row['page_text'] = highlight_and_clip_search_result($row['page_text'], $word_list, $case_sensitive);
   455       if ( strlen($row['page_text']) > 250 && !preg_match('/^\.\.\.(.+)\.\.\.$/', $row['page_text']) )
   455 			if ( strlen($row['page_text']) > 250 && !preg_match('/^\.\.\.(.+)\.\.\.$/', $row['page_text']) )
   456       {
   456 			{
   457         $row['page_text'] = substr($row['page_text'], 0, 150) . '...';
   457 				$row['page_text'] = substr($row['page_text'], 0, 150) . '...';
   458       }
   458 			}
   459       $row['page_name'] = highlight_search_result($row['page_name'], $word_list, $case_sensitive);
   459 			$row['page_name'] = highlight_search_result($row['page_name'], $word_list, $case_sensitive);
   460 
   460 
   461       $page_data[$row['id']] = $row;
   461 			$page_data[$row['id']] = $row;
   462     }
   462 		}
   463     while ( $row = $db->fetchrow() );
   463 		while ( $row = $db->fetchrow() );
   464   }
   464 	}
   465   $db->free_result();
   465 	$db->free_result();
   466   
   466 	
   467   //
   467 	//
   468   // STAGE 5 - SPECIAL PAGE TITLE SEARCH
   468 	// STAGE 5 - SPECIAL PAGE TITLE SEARCH
   469   // Iterate through $paths->pages and check the titles for search terms. Score accordingly.
   469 	// Iterate through $paths->pages and check the titles for search terms. Score accordingly.
   470   //
   470 	//
   471 
   471 
   472   foreach ( $paths->pages as $id => $page )
   472 	foreach ( $paths->pages as $id => $page )
   473   {
   473 	{
   474     if ( $page['namespace'] != 'Special' || $page['visible'] == 0 )
   474 		if ( $page['namespace'] != 'Special' || $page['visible'] == 0 )
   475       continue;
   475 			continue;
   476     $idstring = 'ns=' . $page['namespace'] . ';pid=' . $page['urlname_nons'];
   476 		$idstring = 'ns=' . $page['namespace'] . ';pid=' . $page['urlname_nons'];
   477     $any = array_values(array_unique(array_merge($query['any'], $query_phrase['any'])));
   477 		$any = array_values(array_unique(array_merge($query['any'], $query_phrase['any'])));
   478     foreach ( $any as $term )
   478 		foreach ( $any as $term )
   479     {
   479 		{
   480       if ( $case_sensitive )
   480 			if ( $case_sensitive )
   481       {
   481 			{
   482         if ( strstr($page['name'], $term) || strstr($page['urlname_nons'], $term) )
   482 				if ( strstr($page['name'], $term) || strstr($page['urlname_nons'], $term) )
   483         {
   483 				{
   484           ( isset($scores[$idstring]) ) ? $scores[$idstring] = $scores[$idstring] + 1.5 : $scores[$idstring] = 1.5;
   484 					( isset($scores[$idstring]) ) ? $scores[$idstring] = $scores[$idstring] + 1.5 : $scores[$idstring] = 1.5;
   485         }
   485 				}
   486       }
   486 			}
   487       else
   487 			else
   488       {
   488 			{
   489         if ( stristr($page['name'], $term) || stristr($page['urlname_nons'], $term) )
   489 				if ( stristr($page['name'], $term) || stristr($page['urlname_nons'], $term) )
   490         {
   490 				{
   491           ( isset($scores[$idstring]) ) ? $scores[$idstring] = $scores[$idstring] + 1.5 : $scores[$idstring] = 1.5;
   491 					( isset($scores[$idstring]) ) ? $scores[$idstring] = $scores[$idstring] + 1.5 : $scores[$idstring] = 1.5;
   492         }
   492 				}
   493       }
   493 			}
   494     }
   494 		}
   495     if ( isset($scores[$idstring]) )
   495 		if ( isset($scores[$idstring]) )
   496     {
   496 		{
   497       $page_data[$idstring] = array(
   497 			$page_data[$idstring] = array(
   498           'page_name' => highlight_search_result($page['name'], $word_list, $case_sensitive),
   498 					'page_name' => highlight_search_result($page['name'], $word_list, $case_sensitive),
   499           'page_text' => '',
   499 					'page_text' => '',
   500           'page_id' => $page['urlname_nons'],
   500 					'page_id' => $page['urlname_nons'],
   501           'namespace' => $page['namespace'],
   501 					'namespace' => $page['namespace'],
   502           'score' => $scores[$idstring],
   502 					'score' => $scores[$idstring],
   503           'page_length' => 1,
   503 					'page_length' => 1,
   504           'page_note' => '[' . $lang->get('search_result_tag_special') . ']'
   504 					'page_note' => '[' . $lang->get('search_result_tag_special') . ']'
   505         );
   505 				);
   506     }
   506 		}
   507   }
   507 	}
   508   
   508 	
   509   //
   509 	//
   510   // STAGE 6 - SECOND ELIMINATION ROUND
   510 	// STAGE 6 - SECOND ELIMINATION ROUND
   511   // Iterate through the list of required terms. If a given page is not found to have the required term, eliminate it
   511 	// Iterate through the list of required terms. If a given page is not found to have the required term, eliminate it
   512   //
   512 	//
   513 
   513 
   514   $required = array_merge($query['req'], $query_phrase['req']);
   514 	$required = array_merge($query['req'], $query_phrase['req']);
   515   foreach ( $required as $term )
   515 	foreach ( $required as $term )
   516   {
   516 	{
   517     foreach ( $page_data as $id => $page )
   517 		foreach ( $page_data as $id => $page )
   518     {
   518 		{
   519       if ( ( $page['namespace'] == 'Special' || ( $page['namespace'] != 'Special' && !strstr($page['page_text'], $term) ) ) && !strstr($page['page_id'], $term) && !strstr($page['page_name'], $term) )
   519 			if ( ( $page['namespace'] == 'Special' || ( $page['namespace'] != 'Special' && !strstr($page['page_text'], $term) ) ) && !strstr($page['page_id'], $term) && !strstr($page['page_name'], $term) )
   520       {
   520 			{
   521         unset($page_data[$id]);
   521 				unset($page_data[$id]);
   522       }
   522 			}
   523     }
   523 		}
   524   }
   524 	}
   525 
   525 
   526   // At this point, all of our normal results are in. However, we can also allow plugins to hook into the system and score their own
   526 	// At this point, all of our normal results are in. However, we can also allow plugins to hook into the system and score their own
   527   // pages and add text, etc. as necessary.
   527 	// pages and add text, etc. as necessary.
   528   // Plugins are COMPLETELY responsible for using the search terms and handling Boolean logic properly
   528 	// Plugins are COMPLETELY responsible for using the search terms and handling Boolean logic properly
   529 
   529 
   530   inject_custom_search_results($query, $query_phrase, $scores, $page_data, $case_sensitive, $word_list);
   530 	inject_custom_search_results($query, $query_phrase, $scores, $page_data, $case_sensitive, $word_list);
   531   
   531 	
   532   $code = $plugins->setHook('search_global_inner');
   532 	$code = $plugins->setHook('search_global_inner');
   533   foreach ( $code as $cmd )
   533 	foreach ( $code as $cmd )
   534   {
   534 	{
   535     eval($cmd);
   535 		eval($cmd);
   536   }
   536 	}
   537 
   537 
   538   // a marvelous debugging aid :-)
   538 	// a marvelous debugging aid :-)
   539   // die('<pre>' . htmlspecialchars(print_r($page_data, true)) . '</pre>');
   539 	// die('<pre>' . htmlspecialchars(print_r($page_data, true)) . '</pre>');
   540 
   540 
   541   //
   541 	//
   542   // STAGE 7 - HIGHLIGHT, TRIM, AND SCORE RESULTS
   542 	// STAGE 7 - HIGHLIGHT, TRIM, AND SCORE RESULTS
   543   // We now have the complete results of the search. We need to trim text down to show only portions of the page containing search
   543 	// We now have the complete results of the search. We need to trim text down to show only portions of the page containing search
   544   // terms, highlight any search terms within the page, and sort the final results array in descending order of score.
   544 	// terms, highlight any search terms within the page, and sort the final results array in descending order of score.
   545   //
   545 	//
   546 
   546 
   547   // Sort scores array
   547 	// Sort scores array
   548   arsort($scores);
   548 	arsort($scores);
   549 
   549 
   550   // Divisor for calculating relevance scores
   550 	// Divisor for calculating relevance scores
   551   $divisor = ( count($query['any']) + count($query_phrase['any']) + count($query['req']) + count($query['not']) ) * 1.5;
   551 	$divisor = ( count($query['any']) + count($query_phrase['any']) + count($query['req']) + count($query['not']) ) * 1.5;
   552   $divisor = max($divisor, max($scores));
   552 	$divisor = max($divisor, max($scores));
   553   
   553 	
   554   foreach ( $scores as $page_id => $score )
   554 	foreach ( $scores as $page_id => $score )
   555   {
   555 	{
   556     if ( !isset($page_data[$page_id]) )
   556 		if ( !isset($page_data[$page_id]) )
   557       // It's possible that $scores contains a score for a page that was later eliminated because it contained a disallowed term
   557 			// It's possible that $scores contains a score for a page that was later eliminated because it contained a disallowed term
   558       continue;
   558 			continue;
   559 
   559 
   560     // Make a copy of the datum, then delete the original (it frees up a LOT of RAM)
   560 		// Make a copy of the datum, then delete the original (it frees up a LOT of RAM)
   561     $datum = $page_data[$page_id];
   561 		$datum = $page_data[$page_id];
   562     unset($page_data[$page_id]);
   562 		unset($page_data[$page_id]);
   563 
   563 
   564     // This is an internal value used for sorting - it's no longer needed.
   564 		// This is an internal value used for sorting - it's no longer needed.
   565     unset($datum['id']);
   565 		unset($datum['id']);
   566 
   566 
   567     // Calculate score
   567 		// Calculate score
   568     // if ( $score > $divisor )
   568 		// if ( $score > $divisor )
   569     //   $score = $divisor;
   569 		//   $score = $divisor;
   570     $datum['score'] = round($score / $divisor, 2) * 100;
   570 		$datum['score'] = round($score / $divisor, 2) * 100;
   571     
   571 		
   572     // Highlight the URL
   572 		// Highlight the URL
   573     $datum['url_highlight'] = makeUrlComplete($datum['namespace'], $datum['page_id']);
   573 		$datum['url_highlight'] = makeUrlComplete($datum['namespace'], $datum['page_id']);
   574     $datum['url_highlight'] = preg_replace('/\?.+$/', '', $datum['url_highlight']);
   574 		$datum['url_highlight'] = preg_replace('/\?.+$/', '', $datum['url_highlight']);
   575     $datum['url_highlight'] = highlight_search_result($datum['url_highlight'], $word_list, $case_sensitive);
   575 		$datum['url_highlight'] = highlight_search_result($datum['url_highlight'], $word_list, $case_sensitive);
   576 
   576 
   577     // Store it in our until-now-unused results array
   577 		// Store it in our until-now-unused results array
   578     $results[] = $datum;
   578 		$results[] = $datum;
   579   }
   579 	}
   580 
   580 
   581   // Our work here is done. :-D
   581 	// Our work here is done. :-D
   582   return $results;
   582 	return $results;
   583 }
   583 }
   584 
   584 
   585 /**
   585 /**
   586  * Parses a search query into an associative array. The resultant array will be filled with the following values, each an array:
   586  * Parses a search query into an associative array. The resultant array will be filled with the following values, each an array:
   587  *   any: Search terms that can optionally be present
   587  *   any: Search terms that can optionally be present
   592  * @return array
   592  * @return array
   593  */
   593  */
   594 
   594 
   595 function parse_search_query($query, &$warnings)
   595 function parse_search_query($query, &$warnings)
   596 {
   596 {
   597   global $lang;
   597 	global $lang;
   598   
   598 	
   599   $stopwords = get_stopwords();
   599 	$stopwords = get_stopwords();
   600   $ret = array(
   600 	$ret = array(
   601     'any' => array(),
   601 		'any' => array(),
   602     'req' => array(),
   602 		'req' => array(),
   603     'not' => array()
   603 		'not' => array()
   604     );
   604 		);
   605   $warnings = array();
   605 	$warnings = array();
   606   $terms = array();
   606 	$terms = array();
   607   $in_quote = false;
   607 	$in_quote = false;
   608   $start_term = 0;
   608 	$start_term = 0;
   609   $just_finished = false;
   609 	$just_finished = false;
   610   for ( $i = 0; $i < strlen($query); $i++ )
   610 	for ( $i = 0; $i < strlen($query); $i++ )
   611   {
   611 	{
   612     $chr = $query{$i};
   612 		$chr = $query{$i};
   613     $prev = ( $i > 0 ) ? $query{ $i - 1 } : '';
   613 		$prev = ( $i > 0 ) ? $query{ $i - 1 } : '';
   614     $next = ( ( $i + 1 ) < strlen($query) ) ? $query{ $i + 1 } : '';
   614 		$next = ( ( $i + 1 ) < strlen($query) ) ? $query{ $i + 1 } : '';
   615 
   615 
   616     if ( ( $chr == ' ' && !$in_quote ) || ( $i + 1 == strlen ( $query ) ) )
   616 		if ( ( $chr == ' ' && !$in_quote ) || ( $i + 1 == strlen ( $query ) ) )
   617     {
   617 		{
   618       $len = ( $next == '' ) ? $i + 1 : $i - $start_term;
   618 			$len = ( $next == '' ) ? $i + 1 : $i - $start_term;
   619       $word = substr ( $query, $start_term, $len );
   619 			$word = substr ( $query, $start_term, $len );
   620       $terms[] = $word;
   620 			$terms[] = $word;
   621       $start_term = $i + 1;
   621 			$start_term = $i + 1;
   622     }
   622 		}
   623 
   623 
   624     elseif ( $chr == '"' && $in_quote && $prev != '\\' )
   624 		elseif ( $chr == '"' && $in_quote && $prev != '\\' )
   625     {
   625 		{
   626       $word = substr ( $query, $start_term, $i - $start_term + 1 );
   626 			$word = substr ( $query, $start_term, $i - $start_term + 1 );
   627       $start_pos = ( $next == ' ' ) ? $i + 2 : $i + 1;
   627 			$start_pos = ( $next == ' ' ) ? $i + 2 : $i + 1;
   628       $in_quote = false;
   628 			$in_quote = false;
   629     }
   629 		}
   630 
   630 
   631     elseif ( $chr == '"' && !$in_quote )
   631 		elseif ( $chr == '"' && !$in_quote )
   632     {
   632 		{
   633       $in_quote = true;
   633 			$in_quote = true;
   634       $start_pos = $i;
   634 			$start_pos = $i;
   635     }
   635 		}
   636 
   636 
   637   }
   637 	}
   638 
   638 
   639   $ticker = 0;
   639 	$ticker = 0;
   640 
   640 
   641   foreach ( $terms as $element => $__unused )
   641 	foreach ( $terms as $element => $__unused )
   642   {
   642 	{
   643     $atom =& $terms[$element];
   643 		$atom =& $terms[$element];
   644 
   644 
   645     $ticker++;
   645 		$ticker++;
   646 
   646 
   647     if ( $ticker == 20 )
   647 		if ( $ticker == 20 )
   648     {
   648 		{
   649       $warnings[] = $lang->get('search_err_query_too_many_terms');
   649 			$warnings[] = $lang->get('search_err_query_too_many_terms');
   650       break;
   650 			break;
   651     }
   651 		}
   652 
   652 
   653     if ( substr ( $atom, 0, 2 ) == '+"' && substr ( $atom, ( strlen ( $atom ) - 1 ), 1 ) == '"' )
   653 		if ( substr ( $atom, 0, 2 ) == '+"' && substr ( $atom, ( strlen ( $atom ) - 1 ), 1 ) == '"' )
   654     {
   654 		{
   655       $word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
   655 			$word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
   656       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   656 			if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   657       {
   657 			{
   658         $warnings[] = $lang->get('search_err_query_has_stopwords');
   658 				$warnings[] = $lang->get('search_err_query_has_stopwords');
   659         $ticker--;
   659 				$ticker--;
   660         continue;
   660 				continue;
   661       }
   661 			}
   662       if(in_array($word, $ret['req']))
   662 			if(in_array($word, $ret['req']))
   663       {
   663 			{
   664         $warnings[] = $lang->get('search_err_query_dup_terms');
   664 				$warnings[] = $lang->get('search_err_query_dup_terms');
   665         $ticker--;
   665 				$ticker--;
   666         continue;
   666 				continue;
   667       }
   667 			}
   668       $ret['req'][] = $word;
   668 			$ret['req'][] = $word;
   669     }
   669 		}
   670     elseif ( substr ( $atom, 0, 2 ) == '-"' && substr ( $atom, ( strlen ( $atom ) - 1 ), 1 ) == '"' )
   670 		elseif ( substr ( $atom, 0, 2 ) == '-"' && substr ( $atom, ( strlen ( $atom ) - 1 ), 1 ) == '"' )
   671     {
   671 		{
   672       $word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
   672 			$word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
   673       if ( strlen ( $word ) < 4 )
   673 			if ( strlen ( $word ) < 4 )
   674       {
   674 			{
   675         $warnings[] = $lang->get('search_err_query_term_too_short');
   675 				$warnings[] = $lang->get('search_err_query_term_too_short');
   676         $ticker--;
   676 				$ticker--;
   677         continue;
   677 				continue;
   678       }
   678 			}
   679       if(in_array($word, $ret['not']))
   679 			if(in_array($word, $ret['not']))
   680       {
   680 			{
   681         $warnings[] = $lang->get('search_err_query_dup_terms');
   681 				$warnings[] = $lang->get('search_err_query_dup_terms');
   682         $ticker--;
   682 				$ticker--;
   683         continue;
   683 				continue;
   684       }
   684 			}
   685       $ret['not'][] = $word;
   685 			$ret['not'][] = $word;
   686     }
   686 		}
   687     elseif ( substr ( $atom, 0, 1 ) == '+' )
   687 		elseif ( substr ( $atom, 0, 1 ) == '+' )
   688     {
   688 		{
   689       $word = substr ( $atom, 1 );
   689 			$word = substr ( $atom, 1 );
   690       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   690 			if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   691       {
   691 			{
   692         $warnings[] = $lang->get('search_err_query_has_stopwords');
   692 				$warnings[] = $lang->get('search_err_query_has_stopwords');
   693         $ticker--;
   693 				$ticker--;
   694         continue;
   694 				continue;
   695       }
   695 			}
   696       if(in_array($word, $ret['req']))
   696 			if(in_array($word, $ret['req']))
   697       {
   697 			{
   698         $warnings[] = $lang->get('search_err_query_dup_terms');
   698 				$warnings[] = $lang->get('search_err_query_dup_terms');
   699         $ticker--;
   699 				$ticker--;
   700         continue;
   700 				continue;
   701       }
   701 			}
   702       $ret['req'][] = $word;
   702 			$ret['req'][] = $word;
   703     }
   703 		}
   704     elseif ( substr ( $atom, 0, 1 ) == '-' )
   704 		elseif ( substr ( $atom, 0, 1 ) == '-' )
   705     {
   705 		{
   706       $word = substr ( $atom, 1 );
   706 			$word = substr ( $atom, 1 );
   707       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   707 			if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   708       {
   708 			{
   709         $warnings[] = $lang->get('search_err_query_has_stopwords');
   709 				$warnings[] = $lang->get('search_err_query_has_stopwords');
   710         $ticker--;
   710 				$ticker--;
   711         continue;
   711 				continue;
   712       }
   712 			}
   713       if(in_array($word, $ret['not']))
   713 			if(in_array($word, $ret['not']))
   714       {
   714 			{
   715         $warnings[] = $lang->get('search_err_query_dup_terms');
   715 				$warnings[] = $lang->get('search_err_query_dup_terms');
   716         $ticker--;
   716 				$ticker--;
   717         continue;
   717 				continue;
   718       }
   718 			}
   719       $ret['not'][] = $word;
   719 			$ret['not'][] = $word;
   720     }
   720 		}
   721     elseif ( substr ( $atom, 0, 1 ) == '"' && substr ( $atom, ( strlen($atom) - 1 ), 1 ) == '"' )
   721 		elseif ( substr ( $atom, 0, 1 ) == '"' && substr ( $atom, ( strlen($atom) - 1 ), 1 ) == '"' )
   722     {
   722 		{
   723       $word = substr ( $atom, 1, ( strlen ( $atom ) - 2 ) );
   723 			$word = substr ( $atom, 1, ( strlen ( $atom ) - 2 ) );
   724       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   724 			if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   725       {
   725 			{
   726         $warnings[] = $lang->get('search_err_query_has_stopwords');
   726 				$warnings[] = $lang->get('search_err_query_has_stopwords');
   727         $ticker--;
   727 				$ticker--;
   728         continue;
   728 				continue;
   729       }
   729 			}
   730       if(in_array($word, $ret['any']))
   730 			if(in_array($word, $ret['any']))
   731       {
   731 			{
   732         $warnings[] = $lang->get('search_err_query_dup_terms');
   732 				$warnings[] = $lang->get('search_err_query_dup_terms');
   733         $ticker--;
   733 				$ticker--;
   734         continue;
   734 				continue;
   735       }
   735 			}
   736       $ret['any'][] = $word;
   736 			$ret['any'][] = $word;
   737     }
   737 		}
   738     else
   738 		else
   739     {
   739 		{
   740       $word = $atom;
   740 			$word = $atom;
   741       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   741 			if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
   742       {
   742 			{
   743         $warnings[] = $lang->get('search_err_query_has_stopwords');
   743 				$warnings[] = $lang->get('search_err_query_has_stopwords');
   744         $ticker--;
   744 				$ticker--;
   745         continue;
   745 				continue;
   746       }
   746 			}
   747       if(in_array($word, $ret['any']))
   747 			if(in_array($word, $ret['any']))
   748       {
   748 			{
   749         $warnings[] = $lang->get('search_err_query_dup_terms');
   749 				$warnings[] = $lang->get('search_err_query_dup_terms');
   750         $ticker--;
   750 				$ticker--;
   751         continue;
   751 				continue;
   752       }
   752 			}
   753       $ret['any'][] = $word;
   753 			$ret['any'][] = $word;
   754     }
   754 		}
   755   }
   755 	}
   756   return $ret;
   756 	return $ret;
   757 }
   757 }
   758 
   758 
   759 /**
   759 /**
   760  * Escapes a string for use in a LIKE clause.
   760  * Escapes a string for use in a LIKE clause.
   761  * @param string
   761  * @param string
   762  * @return string
   762  * @return string
   763  */
   763  */
   764 
   764 
   765 function escape_string_like($string)
   765 function escape_string_like($string)
   766 {
   766 {
   767   global $db, $session, $paths, $template, $plugins; // Common objects
   767 	global $db, $session, $paths, $template, $plugins; // Common objects
   768   $string = $db->escape($string);
   768 	$string = $db->escape($string);
   769   $string = str_replace(array('%', '_'), array('\%', '\_'), $string);
   769 	$string = str_replace(array('%', '_'), array('\%', '\_'), $string);
   770   return $string;
   770 	return $string;
   771 }
   771 }
   772 
   772 
   773 /**
   773 /**
   774  * Wraps <highlight></highlight> tags around all words in both the specified array. Does not perform any clipping.
   774  * Wraps <highlight></highlight> tags around all words in both the specified array. Does not perform any clipping.
   775  * @param string Text to process
   775  * @param string Text to process
   778  * @return string
   778  * @return string
   779  */
   779  */
   780 
   780 
   781 function highlight_search_result($pt, $words, $case_sensitive = false)
   781 function highlight_search_result($pt, $words, $case_sensitive = false)
   782 {
   782 {
   783   $words2 = array();
   783 	$words2 = array();
   784   for ( $i = 0; $i < sizeof($words); $i++)
   784 	for ( $i = 0; $i < sizeof($words); $i++)
   785   {
   785 	{
   786     if(!empty($words[$i]))
   786 		if(!empty($words[$i]))
   787       $words2[] = preg_quote($words[$i]);
   787 			$words2[] = preg_quote($words[$i]);
   788   }
   788 	}
   789 
   789 
   790   $flag = ( $case_sensitive ) ? '' : 'i';
   790 	$flag = ( $case_sensitive ) ? '' : 'i';
   791   $regex = '/(' . implode('|', str_replace('/', '\\/', $words2)) . ')/' . $flag;
   791 	$regex = '/(' . implode('|', str_replace('/', '\\/', $words2)) . ')/' . $flag;
   792   $pt = preg_replace($regex, '<highlight>\\1</highlight>', $pt);
   792 	$pt = preg_replace($regex, '<highlight>\\1</highlight>', $pt);
   793 
   793 
   794   return $pt;
   794 	return $pt;
   795 }
   795 }
   796 
   796 
   797 /**
   797 /**
   798  * Wraps <highlight></highlight> tags around all words in both the specified array and the specified text and clips the text to
   798  * Wraps <highlight></highlight> tags around all words in both the specified array and the specified text and clips the text to
   799  * an appropriate length.
   799  * an appropriate length.
   803  * @return string
   803  * @return string
   804  */
   804  */
   805 
   805 
   806 function highlight_and_clip_search_result($pt, $words, $case_sensitive = false)
   806 function highlight_and_clip_search_result($pt, $words, $case_sensitive = false)
   807 {
   807 {
   808   $cut_off = false;
   808 	$cut_off = false;
   809 
   809 
   810   $space_chars = Array("\t", "\n", "\r", " ");
   810 	$space_chars = Array("\t", "\n", "\r", " ");
   811 
   811 
   812   $pt = highlight_search_result($pt, $words, $case_sensitive);
   812 	$pt = highlight_search_result($pt, $words, $case_sensitive);
   813 
   813 
   814   foreach ( $words as $word )
   814 	foreach ( $words as $word )
   815   {
   815 	{
   816     // Boldface searched words
   816 		// Boldface searched words
   817     $ptlen = strlen($pt);
   817 		$ptlen = strlen($pt);
   818     for ( $i = 0; $i < $ptlen; $i++ )
   818 		for ( $i = 0; $i < $ptlen; $i++ )
   819     {
   819 		{
   820       $len = strlen($word);
   820 			$len = strlen($word);
   821       if ( strtolower(substr($pt, $i, $len)) == strtolower($word) )
   821 			if ( strtolower(substr($pt, $i, $len)) == strtolower($word) )
   822       {
   822 			{
   823         $chunk1 = substr($pt, 0, $i);
   823 				$chunk1 = substr($pt, 0, $i);
   824         $chunk2 = substr($pt, $i, $len);
   824 				$chunk2 = substr($pt, $i, $len);
   825         $chunk3 = substr($pt, ( $i + $len ));
   825 				$chunk3 = substr($pt, ( $i + $len ));
   826         $pt = $chunk1 . $chunk2 . $chunk3;
   826 				$pt = $chunk1 . $chunk2 . $chunk3;
   827         $ptlen = strlen($pt);
   827 				$ptlen = strlen($pt);
   828         // Cut off text to 150 chars or so
   828 				// Cut off text to 150 chars or so
   829         if ( !$cut_off )
   829 				if ( !$cut_off )
   830         {
   830 				{
   831           $cut_off = true;
   831 					$cut_off = true;
   832           if ( $i - 75 > 0 )
   832 					if ( $i - 75 > 0 )
   833           {
   833 					{
   834             // Navigate backwards until a space character is found
   834 						// Navigate backwards until a space character is found
   835             $chunk = substr($pt, 0, ( $i - 75 ));
   835 						$chunk = substr($pt, 0, ( $i - 75 ));
   836             $final_chunk = $chunk;
   836 						$final_chunk = $chunk;
   837             for ( $j = strlen($chunk) - 1; $j > 0; $j = $j - 1 )
   837 						for ( $j = strlen($chunk) - 1; $j > 0; $j = $j - 1 )
   838             {
   838 						{
   839               if ( in_array($chunk{$j}, $space_chars) )
   839 							if ( in_array($chunk{$j}, $space_chars) )
   840               {
   840 							{
   841                 $final_chunk = substr($chunk, $j + 1);
   841 								$final_chunk = substr($chunk, $j + 1);
   842                 break;
   842 								break;
   843               }
   843 							}
   844             }
   844 						}
   845             $mid_chunk = substr($pt, ( $i - 75 ), 75);
   845 						$mid_chunk = substr($pt, ( $i - 75 ), 75);
   846 
   846 
   847             $clipped = '...' . $final_chunk . $mid_chunk . $chunk2;
   847 						$clipped = '...' . $final_chunk . $mid_chunk . $chunk2;
   848 
   848 
   849             $chunk = substr($pt, ( $i + strlen($chunk2) + 75 ));
   849 						$chunk = substr($pt, ( $i + strlen($chunk2) + 75 ));
   850             $final_chunk = $chunk;
   850 						$final_chunk = $chunk;
   851             for ( $j = 0; $j < strlen($chunk); $j++ )
   851 						for ( $j = 0; $j < strlen($chunk); $j++ )
   852             {
   852 						{
   853               if ( in_array($chunk{$j}, $space_chars) )
   853 							if ( in_array($chunk{$j}, $space_chars) )
   854               {
   854 							{
   855                 $final_chunk = substr($chunk, 0, $j);
   855 								$final_chunk = substr($chunk, 0, $j);
   856                 break;
   856 								break;
   857               }
   857 							}
   858             }
   858 						}
   859 
   859 
   860             $end_chunk = substr($pt, ( $i + strlen($chunk2) ), 75 );
   860 						$end_chunk = substr($pt, ( $i + strlen($chunk2) ), 75 );
   861 
   861 
   862             $clipped .= $end_chunk . $final_chunk . '...';
   862 						$clipped .= $end_chunk . $final_chunk . '...';
   863 
   863 
   864             $pt = $clipped;
   864 						$pt = $clipped;
   865           }
   865 					}
   866           else if ( strlen($pt) > 200 )
   866 					else if ( strlen($pt) > 200 )
   867           {
   867 					{
   868             $mid_chunk = substr($pt, ( $i - 75 ), 75);
   868 						$mid_chunk = substr($pt, ( $i - 75 ), 75);
   869 
   869 
   870             $clipped = $chunk1 . $chunk2;
   870 						$clipped = $chunk1 . $chunk2;
   871 
   871 
   872             $chunk = substr($pt, ( $i + strlen($chunk2) + 75 ));
   872 						$chunk = substr($pt, ( $i + strlen($chunk2) + 75 ));
   873             $final_chunk = $chunk;
   873 						$final_chunk = $chunk;
   874             for ( $j = 0; $j < strlen($chunk); $j++ )
   874 						for ( $j = 0; $j < strlen($chunk); $j++ )
   875             {
   875 						{
   876               if ( in_array($chunk{$j}, $space_chars) )
   876 							if ( in_array($chunk{$j}, $space_chars) )
   877               {
   877 							{
   878                 $final_chunk = substr($chunk, 0, $j);
   878 								$final_chunk = substr($chunk, 0, $j);
   879                 break;
   879 								break;
   880               }
   880 							}
   881             }
   881 						}
   882 
   882 
   883             $end_chunk = substr($pt, ( $i + strlen($chunk2) ), 75 );
   883 						$end_chunk = substr($pt, ( $i + strlen($chunk2) ), 75 );
   884 
   884 
   885             $clipped .= $end_chunk . $final_chunk . '...';
   885 						$clipped .= $end_chunk . $final_chunk . '...';
   886 
   886 
   887             $pt = $clipped;
   887 						$pt = $clipped;
   888 
   888 
   889           }
   889 					}
   890           break 2;
   890 					break 2;
   891         }
   891 				}
   892       }
   892 			}
   893     }
   893 		}
   894     $cut_off = false;
   894 		$cut_off = false;
   895   }
   895 	}
   896   return $pt;
   896 	return $pt;
   897 }
   897 }
   898 
   898 
   899 /**
   899 /**
   900  * Returns a list of words that shouldn't under most circumstances be indexed for searching.
   900  * Returns a list of words that shouldn't under most circumstances be indexed for searching.
   901  * @return array
   901  * @return array
   902  */
   902  */
   903 
   903 
   904 function get_stopwords()
   904 function get_stopwords()
   905 {
   905 {
   906   static $stopwords;
   906 	static $stopwords;
   907   if ( is_array($stopwords) )
   907 	if ( is_array($stopwords) )
   908     return $stopwords;
   908 		return $stopwords;
   909 
   909 
   910   $stopwords = array('I', 'a', 'about', 'an', 'are', 'as', 'at', 'be', 'by', 'com', 'de', 'en', 'for', 'from', 'how', 'in', 'is', 'it',
   910 	$stopwords = array('I', 'a', 'about', 'an', 'are', 'as', 'at', 'be', 'by', 'com', 'de', 'en', 'for', 'from', 'how', 'in', 'is', 'it',
   911                      'la', 'of', 'on', 'or', 'that', 'the', 'this', 'to', 'was', 'what', 'when', 'where', 'who', 'will', 'with', 'and',
   911  										'la', 'of', 'on', 'or', 'that', 'the', 'this', 'to', 'was', 'what', 'when', 'where', 'who', 'will', 'with', 'and',
   912                      'the');
   912  										'the');
   913   
   913 	
   914   return $stopwords;
   914 	return $stopwords;
   915 }
   915 }
   916 
   916 
   917 /**
   917 /**
   918  * Private function to inject custom results into a search.
   918  * Private function to inject custom results into a search.
   919  */
   919  */
   920 
   920 
   921 function inject_custom_search_results(&$query, &$query_phrase, &$scores, &$page_data, &$case_sensitive, &$word_list)
   921 function inject_custom_search_results(&$query, &$query_phrase, &$scores, &$page_data, &$case_sensitive, &$word_list)
   922 {
   922 {
   923   global $db, $session, $paths, $template, $plugins; // Common objects
   923 	global $db, $session, $paths, $template, $plugins; // Common objects
   924   global $lang;
   924 	global $lang;
   925   
   925 	
   926   global $search_handlers;
   926 	global $search_handlers;
   927   
   927 	
   928   // global functions
   928 	// global functions
   929   $terms = array(
   929 	$terms = array(
   930       'any' => array_merge($query['any'], $query_phrase['any']),
   930 			'any' => array_merge($query['any'], $query_phrase['any']),
   931       'req' => array_merge($query['req'], $query_phrase['req']),
   931 			'req' => array_merge($query['req'], $query_phrase['req']),
   932       'not' => $query['not']
   932 			'not' => $query['not']
   933     );
   933 		);
   934   
   934 	
   935   foreach ( $search_handlers as &$options )
   935 	foreach ( $search_handlers as &$options )
   936   {
   936 	{
   937     $where = array('any' => array(), 'req' => array(), 'not' => array());
   937 		$where = array('any' => array(), 'req' => array(), 'not' => array());
   938     $where_any =& $where['any'];
   938 		$where_any =& $where['any'];
   939     $where_req =& $where['req'];
   939 		$where_req =& $where['req'];
   940     $where_not =& $where['not'];
   940 		$where_not =& $where['not'];
   941     $title_col = ( $case_sensitive ) ? $options['titlecolumn'] : ENANO_SQLFUNC_LOWERCASE . '(' . $options['titlecolumn'] . ')';
   941 		$title_col = ( $case_sensitive ) ? $options['titlecolumn'] : ENANO_SQLFUNC_LOWERCASE . '(' . $options['titlecolumn'] . ')';
   942     if ( isset($options['datacolumn']) )
   942 		if ( isset($options['datacolumn']) )
   943       $desc_col = ( $case_sensitive ) ? $options['datacolumn'] : ENANO_SQLFUNC_LOWERCASE . '(' . $options['datacolumn'] . ')';
   943 			$desc_col = ( $case_sensitive ) ? $options['datacolumn'] : ENANO_SQLFUNC_LOWERCASE . '(' . $options['datacolumn'] . ')';
   944     else
   944 		else
   945       $desc_col = "''";
   945 			$desc_col = "''";
   946     foreach ( $terms['any'] as $term )
   946 		foreach ( $terms['any'] as $term )
   947     {
   947 		{
   948       $term = escape_string_like($term);
   948 			$term = escape_string_like($term);
   949       if ( !$case_sensitive )
   949 			if ( !$case_sensitive )
   950         $term = strtolower($term);
   950 				$term = strtolower($term);
   951       $where_any[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
   951 			$where_any[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
   952     }
   952 		}
   953     foreach ( $terms['req'] as $term )
   953 		foreach ( $terms['req'] as $term )
   954     {
   954 		{
   955       $term = escape_string_like($term);
   955 			$term = escape_string_like($term);
   956       if ( !$case_sensitive )
   956 			if ( !$case_sensitive )
   957         $term = strtolower($term);
   957 				$term = strtolower($term);
   958       $where_req[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
   958 			$where_req[] = "( $title_col LIKE '%{$term}%' OR $desc_col LIKE '%{$term}%' )";
   959     }
   959 		}
   960     foreach ( $terms['not'] as $term )
   960 		foreach ( $terms['not'] as $term )
   961     {
   961 		{
   962       $term = escape_string_like($term);
   962 			$term = escape_string_like($term);
   963       if ( !$case_sensitive )
   963 			if ( !$case_sensitive )
   964         $term = strtolower($term);
   964 				$term = strtolower($term);
   965       $where_not[] = "$title_col NOT LIKE '%{$term}%' AND $desc_col NOT LIKE '%{$term}%'";
   965 			$where_not[] = "$title_col NOT LIKE '%{$term}%' AND $desc_col NOT LIKE '%{$term}%'";
   966     }
   966 		}
   967     if ( empty($where_any) )
   967 		if ( empty($where_any) )
   968       unset($where_any, $where['any']);
   968 			unset($where_any, $where['any']);
   969     if ( empty($where_req) )
   969 		if ( empty($where_req) )
   970       unset($where_req, $where['req']);
   970 			unset($where_req, $where['req']);
   971     if ( empty($where_not) )
   971 		if ( empty($where_not) )
   972       unset($where_not, $where['not']);
   972 			unset($where_not, $where['not']);
   973     
   973 		
   974     $where_any = '(' . implode(' OR ', $where_any) . '' . ( isset($where['req']) || isset($where['not']) ? ' OR 1 = 1' : '' ) . ')';
   974 		$where_any = '(' . implode(' OR ', $where_any) . '' . ( isset($where['req']) || isset($where['not']) ? ' OR 1 = 1' : '' ) . ')';
   975     
   975 		
   976     if ( isset($where_req) )
   976 		if ( isset($where_req) )
   977       $where_req = implode(' AND ', $where_req);
   977 			$where_req = implode(' AND ', $where_req);
   978     if ( isset($where_not) )
   978 		if ( isset($where_not) )
   979     $where_not = implode( 'AND ', $where_not);
   979 		$where_not = implode( 'AND ', $where_not);
   980     
   980 		
   981     $where = implode(' AND ', $where);
   981 		$where = implode(' AND ', $where);
   982     
   982 		
   983     $columns = $options['titlecolumn'];
   983 		$columns = $options['titlecolumn'];
   984     if ( isset($options['datacolumn']) )
   984 		if ( isset($options['datacolumn']) )
   985       $columns .= ", {$options['datacolumn']}";
   985 			$columns .= ", {$options['datacolumn']}";
   986     if ( isset($options['additionalcolumns']) )
   986 		if ( isset($options['additionalcolumns']) )
   987       $columns .= ', ' . implode(', ', $options['additionalcolumns']);
   987 			$columns .= ', ' . implode(', ', $options['additionalcolumns']);
   988     
   988 		
   989     $additionalwhere = ( isset($options['additionalwhere']) ) ? $options['additionalwhere'] : '';
   989 		$additionalwhere = ( isset($options['additionalwhere']) ) ? $options['additionalwhere'] : '';
   990     
   990 		
   991     $sql = "SELECT $columns FROM " . table_prefix . "{$options['table']} WHERE ( $where ) $additionalwhere;";
   991 		$sql = "SELECT $columns FROM " . table_prefix . "{$options['table']} WHERE ( $where ) $additionalwhere;";
   992   
   992 	
   993     if ( !($q = $db->sql_unbuffered_query($sql)) )
   993 		if ( !($q = $db->sql_unbuffered_query($sql)) )
   994     {
   994 		{
   995       $db->_die('Automatically generated search query');
   995 			$db->_die('Automatically generated search query');
   996     }
   996 		}
   997     
   997 		
   998     if ( $row = $db->fetchrow() )
   998 		if ( $row = $db->fetchrow() )
   999     {
   999 		{
  1000       do
  1000 			do
  1001       {
  1001 			{
  1002         $parser = $template->makeParserText($options['uniqueid']);
  1002 				$parser = $template->makeParserText($options['uniqueid']);
  1003         $parser->assign_vars($row);
  1003 				$parser->assign_vars($row);
  1004         $idstring = $parser->run();
  1004 				$idstring = $parser->run();
  1005         
  1005 				
  1006         // Score this result
  1006 				// Score this result
  1007         foreach ( $word_list as $term )
  1007 				foreach ( $word_list as $term )
  1008         {
  1008 				{
  1009           if ( $case_sensitive )
  1009 					if ( $case_sensitive )
  1010           {
  1010 					{
  1011             if ( strstr($row[$options['titlecolumn']], $term) )
  1011 						if ( strstr($row[$options['titlecolumn']], $term) )
  1012             {
  1012 						{
  1013               ( isset($scores[$idstring]) ) ? $scores[$idstring] += 1.5 : $scores[$idstring] = 1.5;
  1013 							( isset($scores[$idstring]) ) ? $scores[$idstring] += 1.5 : $scores[$idstring] = 1.5;
  1014             }
  1014 						}
  1015             else if ( isset($options['datacolumn']) && strstr($row[$options['datacolumn']], $term) )
  1015 						else if ( isset($options['datacolumn']) && strstr($row[$options['datacolumn']], $term) )
  1016             {
  1016 						{
  1017               ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
  1017 							( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
  1018             }
  1018 						}
  1019           }
  1019 					}
  1020           else
  1020 					else
  1021           {
  1021 					{
  1022             if ( stristr($row[$options['titlecolumn']], $term) )
  1022 						if ( stristr($row[$options['titlecolumn']], $term) )
  1023             {
  1023 						{
  1024               ( isset($scores[$idstring]) ) ? $scores[$idstring] += 1.5 : $scores[$idstring] = 1.5;
  1024 							( isset($scores[$idstring]) ) ? $scores[$idstring] += 1.5 : $scores[$idstring] = 1.5;
  1025             }
  1025 						}
  1026             else if ( isset($options['datacolumn']) && stristr($row[$options['datacolumn']], $term) )
  1026 						else if ( isset($options['datacolumn']) && stristr($row[$options['datacolumn']], $term) )
  1027             {
  1027 						{
  1028               ( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
  1028 							( isset($scores[$idstring]) ) ? $scores[$idstring]++ : $scores[$idstring] = 1;
  1029             }
  1029 						}
  1030           }
  1030 					}
  1031         }
  1031 				}
  1032         // Generate text...
  1032 				// Generate text...
  1033         $text = '';
  1033 				$text = '';
  1034         if ( isset($options['datacolumn']) && !isset($options['formatcallback']) )
  1034 				if ( isset($options['datacolumn']) && !isset($options['formatcallback']) )
  1035         {
  1035 				{
  1036           $text = highlight_and_clip_search_result(htmlspecialchars($row[$options['datacolumn']]), $word_list);
  1036 					$text = highlight_and_clip_search_result(htmlspecialchars($row[$options['datacolumn']]), $word_list);
  1037         }
  1037 				}
  1038         else if ( isset($options['formatcallback']) )
  1038 				else if ( isset($options['formatcallback']) )
  1039         {
  1039 				{
  1040           if ( is_callable($options['formatcallback']) )
  1040 					if ( is_callable($options['formatcallback']) )
  1041           {
  1041 					{
  1042             $text = call_user_func($options['formatcallback'], $row, $word_list);
  1042 						$text = call_user_func($options['formatcallback'], $row, $word_list);
  1043           }
  1043 					}
  1044           else
  1044 					else
  1045           {
  1045 					{
  1046             $parser = $template->makeParserText($options['formatcallback']);
  1046 						$parser = $template->makeParserText($options['formatcallback']);
  1047             $parser->assign_vars($row);
  1047 						$parser->assign_vars($row);
  1048             $text = $parser->run();
  1048 						$text = $parser->run();
  1049           }
  1049 					}
  1050         }
  1050 				}
  1051         
  1051 				
  1052         // Inject result
  1052 				// Inject result
  1053         
  1053 				
  1054         if ( isset($scores[$idstring]) )
  1054 				if ( isset($scores[$idstring]) )
  1055         {
  1055 				{
  1056           $parser = $template->makeParserText($options['linkformat']['page_id']);
  1056 					$parser = $template->makeParserText($options['linkformat']['page_id']);
  1057           $parser->assign_vars($row);
  1057 					$parser->assign_vars($row);
  1058           $page_id = $parser->run();
  1058 					$page_id = $parser->run();
  1059           
  1059 					
  1060           $parser = $template->makeParserText($options['linkformat']['namespace']);
  1060 					$parser = $template->makeParserText($options['linkformat']['namespace']);
  1061           $parser->assign_vars($row);
  1061 					$parser->assign_vars($row);
  1062           $namespace = $parser->run();
  1062 					$namespace = $parser->run();
  1063           
  1063 					
  1064           $page_data[$idstring] = array(
  1064 					$page_data[$idstring] = array(
  1065             'page_name' => highlight_search_result(htmlspecialchars($row[$options['titlecolumn']]), $word_list),
  1065 						'page_name' => highlight_search_result(htmlspecialchars($row[$options['titlecolumn']]), $word_list),
  1066             'page_text' => $text,
  1066 						'page_text' => $text,
  1067             'score' => $scores[$idstring],
  1067 						'score' => $scores[$idstring],
  1068             'page_id' => $page_id,
  1068 						'page_id' => $page_id,
  1069             'namespace' => $namespace,
  1069 						'namespace' => $namespace,
  1070           );
  1070 					);
  1071           
  1071 					
  1072           // Any additional flags that need to be added to the result?
  1072 					// Any additional flags that need to be added to the result?
  1073           // The small usually-bracketed text to the left of the title
  1073 					// The small usually-bracketed text to the left of the title
  1074           if ( isset($options['resultnote']) )
  1074 					if ( isset($options['resultnote']) )
  1075           {
  1075 					{
  1076             $page_data[$idstring]['page_note'] = $options['resultnote'];
  1076 						$page_data[$idstring]['page_note'] = $options['resultnote'];
  1077           }
  1077 					}
  1078           // Should we include the length?
  1078 					// Should we include the length?
  1079           if ( isset($options['datacolumn']) )
  1079 					if ( isset($options['datacolumn']) )
  1080           {
  1080 					{
  1081             $page_data[$idstring]['page_length'] = strlen($row[$options['datacolumn']]);
  1081 						$page_data[$idstring]['page_length'] = strlen($row[$options['datacolumn']]);
  1082           }
  1082 					}
  1083           else
  1083 					else
  1084           {
  1084 					{
  1085             $page_data[$idstring]['page_length'] = 0;
  1085 						$page_data[$idstring]['page_length'] = 0;
  1086             $page_data[$idstring]['zero_length'] = true;
  1086 						$page_data[$idstring]['zero_length'] = true;
  1087           }
  1087 					}
  1088           // Anything to append to result links?
  1088 					// Anything to append to result links?
  1089           if ( isset($options['linkformat']['append']) )
  1089 					if ( isset($options['linkformat']['append']) )
  1090           {
  1090 					{
  1091             $page_data[$idstring]['url_append'] = $options['linkformat']['append'];
  1091 						$page_data[$idstring]['url_append'] = $options['linkformat']['append'];
  1092           }
  1092 					}
  1093         }
  1093 				}
  1094       }
  1094 			}
  1095       while ( $row = $db->fetchrow($q) );
  1095 			while ( $row = $db->fetchrow($q) );
  1096       $db->free_result($q);
  1096 			$db->free_result($q);
  1097     }
  1097 		}
  1098   }
  1098 	}
  1099 }
  1099 }
  1100 
  1100 
  1101 ?>
  1101 ?>