]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/SpecialSearch.php
MediaWiki 1.5.8 (initial commit)
[autoinstallsdev/mediawiki.git] / includes / SpecialSearch.php
1 <?php
2 # Copyright (C) 2004 Brion Vibber <brion@pobox.com>
3 # http://www.mediawiki.org/
4
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or 
8 # (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 # http://www.gnu.org/copyleft/gpl.html
19
20 /**
21  * Run text & title search and display the output
22  * @package MediaWiki
23  * @subpackage SpecialPage
24  */
25
26 /** */
27 require_once( 'SearchEngine.php' );
28 require_once( 'Revision.php' );
29
30 /**
31  * Entry point
32  *
33  * @param string $par (default '')
34  */
35 function wfSpecialSearch( $par = '' ) {
36         global $wgRequest, $wgUser;
37         
38         $search = $wgRequest->getText( 'search', $par );
39         $searchPage = new SpecialSearch( $wgRequest, $wgUser );
40         if( $wgRequest->getVal( 'fulltext' ) ||
41                 !is_null( $wgRequest->getVal( 'offset' ) ) ||
42                 !is_null ($wgRequest->getVal( 'searchx' ) ) ) {
43                 $searchPage->showResults( $search );
44         } else {
45                 $searchPage->goResult( $search );
46         }
47 }
48
49 /**
50  * @todo document
51  * @package MediaWiki
52  * @subpackage SpecialPage
53  */
54 class SpecialSearch {
55
56         /**
57          * Set up basic search parameters from the request and user settings.
58          * Typically you'll pass $wgRequest and $wgUser.
59          *
60          * @param WebRequest $request
61          * @param User $user
62          * @access public
63          */
64         function SpecialSearch( &$request, &$user ) {
65                 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
66                 
67                 if( $request->getCheck( 'searchx' ) ) {
68                         $this->namespaces = $this->powerSearch( $request );
69                 } else {
70                         $this->namespaces = $this->userNamespaces( $user );
71                 }
72                 
73                 $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false;
74         }
75         
76         /**
77          * If an exact title match can be found, jump straight ahead to
78          * @param string $term
79          * @access public
80          */
81         function goResult( $term ) {
82                 global $wgOut;
83                 global $wgGoToEdit;
84                 
85                 $this->setupPage( $term );
86
87                 # Try to go to page as entered.
88                 #
89                 $t = Title::newFromText( $term );
90
91                 # If the string cannot be used to create a title
92                 if( is_null( $t ) ){ 
93                         return $this->showResults( $term );
94                 }
95
96                 # If there's an exact or very near match, jump right there.
97                 $t = SearchEngine::getNearMatch( $term );
98                 if( !is_null( $t ) ) {
99                         $wgOut->redirect( $t->getFullURL() );
100                         return;
101                 }
102                 
103                 # No match, generate an edit URL
104                 $t = Title::newFromText( $term );
105                 if( is_null( $t ) ) {
106                         $editurl = ''; # hrm...
107                 } else {
108                         # If the feature is enabled, go straight to the edit page
109                         if ( $wgGoToEdit ) {
110                                 $wgOut->redirect( $t->getFullURL( 'action=edit' ) );
111                                 return;
112                         } else {
113                                 $editurl = $t->escapeLocalURL( 'action=edit' );
114                         }
115                 }
116                 $wgOut->addWikiText( wfMsg('nogomatch', ":$term" ) );
117
118                 return $this->showResults( $term );
119         }
120         
121         /**
122          * @param string $term
123          * @access public
124          */
125         function showResults( $term ) {
126                 $fname = 'SpecialSearch::showResults';
127                 wfProfileIn( $fname );
128                 
129                 $this->setupPage( $term );
130                 
131                 global $wgUser, $wgOut;
132                 $sk = $wgUser->getSkin();
133                 $wgOut->addWikiText( wfMsg( 'searchresulttext' ) );
134                 
135                 #if ( !$this->parseQuery() ) {
136                 if( '' === trim( $term ) ) {
137                         $wgOut->setSubtitle( '' );
138                         $wgOut->addHTML( $this->powerSearchBox( $term ) );
139                         wfProfileOut( $fname );
140                         return;
141                 }
142                 
143                 global $wgDisableTextSearch;
144                 if ( $wgDisableTextSearch ) {
145                         global $wgForwardSearchUrl;
146                         if( $wgForwardSearchUrl ) {
147                                 $url = str_replace( '$1', urlencode( $term ), $wgForwardSearchUrl );
148                                 $wgOut->redirect( $url );
149                                 return;
150                         }
151                         global $wgInputEncoding;
152                         $wgOut->addHTML( wfMsg( 'searchdisabled' ) );
153                         $wgOut->addHTML(
154                                 wfMsg( 'googlesearch',
155                                         htmlspecialchars( $term ),
156                                         htmlspecialchars( $wgInputEncoding ),
157                                         htmlspecialchars( wfMsg( 'search' ) )
158                                 )
159                         );
160                         wfProfileOut( $fname );
161                         return;
162                 }
163
164                 $search = SearchEngine::create();
165                 $search->setLimitOffset( $this->limit, $this->offset );
166                 $search->setNamespaces( $this->namespaces );
167                 $search->showRedirects = $this->searchRedirects;
168                 $titleMatches = $search->searchTitle( $term );
169                 $textMatches = $search->searchText( $term );
170                 
171                 $num = ( $titleMatches ? $titleMatches->numRows() : 0 )
172                         + ( $textMatches ? $textMatches->numRows() : 0);
173                 if ( $num >= $this->limit ) {
174                         $top = wfShowingResults( $this->offset, $this->limit );
175                 } else {
176                         $top = wfShowingResultsNum( $this->offset, $this->limit, $num );
177                 }
178                 $wgOut->addHTML( "<p>{$top}</p>\n" );
179
180                 if( $num || $this->offset ) {
181                         $prevnext = wfViewPrevNext( $this->offset, $this->limit,
182                                 'Special:Search',
183                                 wfArrayToCGI(
184                                         $this->powerSearchOptions(),
185                                         array( 'search' => $term ) ) );
186                         $wgOut->addHTML( "<br />{$prevnext}\n" );
187                 }
188
189                 if( $titleMatches ) {
190                         if( $titleMatches->numRows() ) {
191                                 $wgOut->addWikiText( '==' . wfMsg( 'titlematches' ) . "==\n" );
192                                 $wgOut->addHTML( $this->showMatches( $titleMatches ) );
193                         } else {
194                                 $wgOut->addWikiText( '==' . wfMsg( 'notitlematches' ) . "==\n" );
195                         }
196                 }
197                 
198                 if( $textMatches ) {
199                         if( $textMatches->numRows() ) {
200                                 $wgOut->addWikiText( '==' . wfMsg( 'textmatches' ) . "==\n" );
201                                 $wgOut->addHTML( $this->showMatches( $textMatches ) );
202                         } elseif( $num == 0 ) {
203                                 # Don't show the 'no text matches' if we received title matches
204                                 $wgOut->addWikiText( '==' . wfMsg( 'notextmatches' ) . "==\n" );
205                         }
206                 }
207                 
208                 if ( $num == 0 ) {
209                         $wgOut->addWikiText( wfMsg( 'nonefound' ) );
210                 }
211                 if( $num || $this->offset ) {
212                         $wgOut->addHTML( "<p>{$prevnext}</p>\n" );
213                 }
214                 $wgOut->addHTML( $this->powerSearchBox( $term ) );
215                 wfProfileOut( $fname );
216         }
217         
218         #------------------------------------------------------------------
219         # Private methods below this line
220         
221         /**
222          * 
223          */
224         function setupPage( $term ) {
225                 global $wgOut;
226                 $wgOut->setPageTitle( wfMsg( 'searchresults' ) );
227                 $wgOut->setSubtitle( htmlspecialchars( wfMsg( 'searchquery', $term ) ) );
228                 $wgOut->setArticleRelated( false );
229                 $wgOut->setRobotpolicy( 'noindex,nofollow' );
230         }
231         
232         /**
233          * Extract default namespaces to search from the given user's
234          * settings, returning a list of index numbers.
235          *
236          * @param User $user
237          * @return array
238          * @access private
239          */
240         function userNamespaces( &$user ) {
241                 $arr = array();
242                 foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
243                         if( $user->getOption( 'searchNs' . $ns ) ) {
244                                 $arr[] = $ns;
245                         }
246                 }
247                 return $arr;
248         }
249         
250         /**
251          * Extract "power search" namespace settings from the request object,
252          * returning a list of index numbers to search.
253          *
254          * @param WebRequest $request
255          * @return array
256          * @access private
257          */
258         function powerSearch( &$request ) {
259                 $arr = array();
260                 foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
261                         if( $request->getCheck( 'ns' . $ns ) ) {
262                                 $arr[] = $ns;
263                         }
264                 }
265                 return $arr;
266         }
267         
268         /**
269          * Reconstruct the 'power search' options for links
270          * @return array
271          * @access private
272          */
273         function powerSearchOptions() {
274                 $opt = array();
275                 foreach( $this->namespaces as $n ) {
276                         $opt['ns' . $n] = 1;
277                 }
278                 $opt['redirs'] = $this->searchRedirects ? 1 : 0;
279                 $opt['searchx'] = 1;
280                 return $opt;
281         }
282         
283         /**
284          * @param SearchResultSet $matches
285          * @param string $terms partial regexp for highlighting terms
286          */
287         function showMatches( &$matches ) {
288                 $fname = 'SpecialSearch::showMatches';
289                 wfProfileIn( $fname );
290                 
291                 global $wgContLang;
292                 $tm = $wgContLang->convertForSearchResult( $matches->termMatches() );
293                 $terms = implode( '|', $tm );
294                 
295                 global $wgOut;
296                 $off = $this->offset + 1;
297                 $out = "<ol start='{$off}'>\n";
298
299                 while( $result = $matches->next() ) {
300                         $out .= $this->showHit( $result, $terms );
301                 }
302                 $out .= "</ol>\n";
303
304                 // convert the whole thing to desired language variant
305                 global $wgContLang;
306                 $out = $wgContLang->convert( $out );
307                 wfProfileOut( $fname );
308                 return $out;
309         }
310         
311         /**
312          * Format a single hit result
313          * @param SearchResult $result
314          * @param string $terms partial regexp for highlighting terms
315          */
316         function showHit( $result, $terms ) {
317                 $fname = 'SpecialSearch::showHit';
318                 wfProfileIn( $fname );
319                 global $wgUser, $wgContLang;
320
321                 $t = $result->getTitle();
322                 if( is_null( $t ) ) {
323                         wfProfileOut( $fname );
324                         return "<!-- Broken link in search result -->\n";
325                 }
326                 $sk =& $wgUser->getSkin();
327
328                 $contextlines = $wgUser->getOption( 'contextlines' );
329                 if ( '' == $contextlines ) { $contextlines = 5; }
330                 $contextchars = $wgUser->getOption( 'contextchars' );
331                 if ( '' == $contextchars ) { $contextchars = 50; }
332
333                 $link = $sk->makeKnownLinkObj( $t );
334                 $revision = Revision::newFromTitle( $t );
335                 $text = $revision->getText();
336                 $size = wfMsg( 'nbytes', strlen( $text ) );
337
338                 $lines = explode( "\n", $text );
339
340                 $max = IntVal( $contextchars ) + 1;
341                 $pat1 = "/(.*)($terms)(.{0,$max})/i";
342
343                 $lineno = 0;
344                 
345                 $extract = '';
346                 wfProfileIn( "$fname-extract" );
347                 foreach ( $lines as $line ) {
348                         if ( 0 == $contextlines ) {
349                                 break;
350                         }
351                         ++$lineno;
352                         if ( ! preg_match( $pat1, $line, $m ) ) {
353                                 continue;
354                         }
355                         --$contextlines;
356                         $pre = $wgContLang->truncate( $m[1], -$contextchars, '...' );
357
358                         if ( count( $m ) < 3 ) {
359                                 $post = '';
360                         } else {
361                                 $post = $wgContLang->truncate( $m[3], $contextchars, '...' );
362                         }
363
364                         $found = $m[2];
365
366                         $line = htmlspecialchars( $pre . $found . $post );
367                         $pat2 = '/(' . $terms . ")/i";
368                         $line = preg_replace( $pat2,
369                           "<span class='searchmatch'>\\1</span>", $line );
370
371                         $extract .= "<br /><small>{$lineno}: {$line}</small>\n";
372                 }
373                 wfProfileOut( "$fname-extract" );
374                 wfProfileOut( $fname );
375                 return "<li>{$link} ({$size}){$extract}</li>\n";
376         }
377         
378         function powerSearchBox( $term ) {
379                 $namespaces = '';
380                 foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
381                         $checked = in_array( $ns, $this->namespaces )
382                                 ? ' checked="checked"'
383                                 : '';
384                         $name = str_replace( '_', ' ', $name );
385                         if( '' == $name ) {
386                                 $name = wfMsg( 'blanknamespace' );
387                         }
388                         $namespaces .= " <label><input type='checkbox' value=\"1\" name=\"" .
389                           "ns{$ns}\"{$checked} />{$name}</label>\n";
390                 }
391                 
392                 $checked = $this->searchRedirects
393                         ? ' checked="checked"'
394                         : '';
395                 $redirect = "<input type='checkbox' value='1' name=\"redirs\"{$checked} />\n";
396                 
397                 $searchField = "<input type='text' name=\"search\" value=\"" .
398                         htmlspecialchars( $term ) ."\" width=\"80\" />\n";
399                 
400                 $searchButton = '<input type="submit" name="searchx" value="' .
401                   htmlspecialchars( wfMsg('powersearch') ) . "\" />\n";
402                 
403                 $ret = wfMsg( 'powersearchtext',
404                         $namespaces, $redirect, $searchField,
405                         '', '', '', '', '', # Dummy placeholders
406                         $searchButton );
407                 
408                 $title = Title::makeTitle( NS_SPECIAL, 'Search' );
409                 $action = $title->escapeLocalURL();
410                 return "<br /><br />\n<form id=\"powersearch\" method=\"get\" " .
411                   "action=\"$action\">\n{$ret}\n</form>\n";
412         }
413 }
414
415 ?>