* 0 => NS_MAIN
[lhc/web/wiklou.git] / includes / SearchEngine.php
1 <?php
2 /**
3 * Contain a class for special pages
4 * @package MediaWiki
5 * @subpackage Search
6 */
7
8 /**
9 * @package MediaWiki
10 */
11 class SearchEngine {
12 var $limit = 10;
13 var $offset = 0;
14 var $searchTerms = array();
15 var $namespaces = array( 0 );
16 var $showRedirects = false;
17
18 /**
19 * Perform a full text search query and return a result set.
20 *
21 * @param string $term - Raw search term
22 * @param array $namespaces - List of namespaces to search
23 * @return ResultWrapper
24 * @access public
25 */
26 function searchText( $term ) {
27 return $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), true ) ) );
28 }
29
30 /**
31 * Perform a title-only search query and return a result set.
32 *
33 * @param string $term - Raw search term
34 * @param array $namespaces - List of namespaces to search
35 * @return ResultWrapper
36 * @access public
37 */
38 function searchTitle( $term ) {
39 return $this->db->resultObject( $this->db->query( $this->getQuery( $this->filter( $term ), false ) ) );
40 }
41
42 /**
43 * If an exact title match can be find, or a very slightly close match,
44 * return the title. If no match, returns NULL.
45 *
46 * @static
47 * @param string $term
48 * @return Title
49 * @access private
50 */
51 function getNearMatch( $term ) {
52 # Exact match? No need to look further.
53 $title = Title::newFromText( $term );
54 if ( $title->getNamespace() == NS_SPECIAL || 0 != $title->getArticleID() ) {
55 return $title;
56 }
57
58 # Now try all lower case (i.e. first letter capitalized)
59 #
60 $title = Title::newFromText( strtolower( $term ) );
61 if ( 0 != $title->getArticleID() ) {
62 return $title;
63 }
64
65 # Now try capitalized string
66 #
67 $title = Title::newFromText( ucwords( strtolower( $term ) ) );
68 if ( 0 != $title->getArticleID() ) {
69 return $title;
70 }
71
72 # Now try all upper case
73 #
74 $title = Title::newFromText( strtoupper( $term ) );
75 if ( 0 != $title->getArticleID() ) {
76 return $title;
77 }
78
79 $title = Title::newFromText( $term );
80
81 # Entering an IP address goes to the contributions page
82 if ( ( $title->getNamespace() == NS_USER && User::isIP($title->getText() ) )
83 || User::isIP( trim( $term ) ) ) {
84 return Title::makeTitle( NS_SPECIAL, "Contributions/" . $title->getDbkey() );
85 }
86
87
88 # Entering a user goes to the user page whether it's there or not
89 if ( $title->getNamespace() == NS_USER ) {
90 return $title;
91 }
92
93 # Quoted term? Try without the quotes...
94 if( preg_match( '/^"([^"]+)"$/', $term, $matches ) ) {
95 return SearchEngine::getNearMatch( $matches[1] );
96 }
97
98 return NULL;
99 }
100
101 function legalSearchChars() {
102 return "A-Za-z_'0-9\\x80-\\xFF\\-";
103 }
104
105 /**
106 * Set the maximum number of results to return
107 * and how many to skip before returning the first.
108 *
109 * @param int $limit
110 * @param int $offset
111 * @access public
112 */
113 function setLimitOffset( $limit, $offset = 0 ) {
114 $this->limit = IntVal( $limit );
115 $this->offset = IntVal( $offset );
116 }
117
118 /**
119 * Set which namespaces the search should include.
120 * Give an array of namespace index numbers.
121 *
122 * @param array $namespaces
123 * @access public
124 */
125 function setNamespaces( $namespaces ) {
126 $this->namespaces = $namespaces;
127 }
128
129 /**
130 * Make a list of searchable namespaces and their canonical names.
131 * @return array
132 * @access public
133 */
134 function searchableNamespaces() {
135 global $wgContLang;
136 $arr = array();
137 foreach( $wgContLang->getNamespaces() as $ns => $name ) {
138 if( $ns >= NS_MAIN ) {
139 $arr[$ns] = $name;
140 }
141 }
142 return $arr;
143 }
144
145 /**
146 * Fetch an array of regular expression fragments for matching
147 * the search terms as parsed by this engine in a text extract.
148 *
149 * @return array
150 * @access public
151 */
152 function termMatches() {
153 return $this->searchTerms;
154 }
155
156 /**
157 * Return a 'cleaned up' search string
158 *
159 * @return string
160 * @access public
161 */
162 function filter( $text ) {
163 $lc = $this->legalSearchChars();
164 return trim( preg_replace( "/[^{$lc}]/", " ", $text ) );
165 }
166
167 /**
168 * Return a partial WHERE clause to exclude redirects, if so set
169 * @return string
170 * @access private
171 */
172 function queryRedirect() {
173 if( $this->showRedirects ) {
174 return 'AND cur_is_redirect=0';
175 } else {
176 return '';
177 }
178 }
179
180 /**
181 * Return a partial WHERE clause to limit the search to the given namespaces
182 * @return string
183 * @access private
184 */
185 function queryNamespaces() {
186 $namespaces = implode( ',', $this->namespaces );
187 if ($namespaces == '') {
188 $namespaces = '0';
189 }
190 return 'AND page_namespace IN (' . $namespaces . ')';
191 }
192
193 /**
194 * Return a LIMIT clause to limit results on the query.
195 * @return string
196 * @access private
197 */
198 function queryLimit() {
199 return $this->db->limitResult( $this->limit, $this->offset );
200 }
201
202 /**
203 * Does not do anything for generic search engine
204 * subclasses may define this though
205 * @return string
206 * @access private
207 */
208 function queryRanking($filteredTerm,$fulltext) {
209 return "";
210 }
211
212 /**
213 * Construct the full SQL query to do the search.
214 * The guts shoulds be constructed in queryMain()
215 * @param string $filteredTerm
216 * @param bool $fulltext
217 * @access private
218 */
219 function getQuery( $filteredTerm, $fulltext ) {
220 return $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
221 $this->queryRedirect() . ' ' .
222 $this->queryNamespaces() . ' ' .
223 $this->queryRanking($filteredTerm, $fulltext) . ' ' .
224 $this->queryLimit();
225 }
226
227 /**
228 * Load up the appropriate search engine class for the currently
229 * active database backend, and return a configured instance.
230 *
231 * @return SearchEngine
232 * @access private
233 */
234 function create() {
235 global $wgDBtype, $wgDBmysql4, $wgSearchType;
236 if( $wgDBtype == 'mysql' ) {
237 if( $wgDBmysql4 ) {
238 $class = 'SearchMySQL4';
239 require_once( 'SearchMySQL4.php' );
240 } else {
241 $class = 'SearchMysql3';
242 require_once( 'SearchMySQL3.php' );
243 }
244 } else if ( $wgDBtype == 'PostgreSQL' ) {
245 $class = 'SearchTsearch2';
246 require_once( 'SearchTsearch2.php' );
247 } else {
248 $class = 'SearchEngineDummy';
249 }
250 $search = new $class( wfGetDB( DB_SLAVE ) );
251 $search->setLimitOffset(0,0);
252 return $search;
253 }
254
255
256 }
257
258 /**
259 * @package MediaWiki
260 */
261 class SearchEngineDummy {
262 function search( $term ) {
263 return null;
264 }
265 }
266