-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.php
372 lines (299 loc) · 12 KB
/
search.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
<?php
/**
* search among pages
*
* This script calls for a search pattern, then actually searches the database.
*
* The request can be limited to only one section. In this case, sub-sections are searched as well.
*
* The integrated search engine is based on full-text indexing capabilities of MySQL.
*
* @link http://dev.mysql.com/doc/mysql/en/Fulltext_Search.html MySQL Manual | 12.6 Full-Text Search Functions
* @link http://www.databasejournal.com/features/mysql/article.php/1578331 Using Fulltext Indexes in MySQL - Part 1
* @link http://www.databasejournal.com/features/mysql/article.php/1587371 Using Fulltext Indexes in MySQL - Part 2, Boolean searches
*
* A link to get search results as a rss feed is offered in an extra box.
*
* @see services/search.php
*
* Small words are removed to avoid users being stucked with unsuccessful searches (Thank you Emmanuel).
*
* Accept following invocations:
* - search.php?search=<keywords>
* - search.php?search=<keywords>&page=1
* - search.php?search=<keywords>&anchor=section:12
*
* @author Bernard Paques
* @tester Guillaume Garnier
* @tester Dobliu
* @tester fw_crocodile
* @tester Aleko
* @tester Vincent Weber
* @author Richard Gilmour
* @tester Antoine Bour
* @tester Emmanuel Beucher
* @tester Manuel Lopez Gallego
* @reference
* @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
*/
// common definitions and initial processing
include_once 'shared/global.php';
include_once 'services/call.php'; // list RSS resources
// prevent attacks
$search = '';
if(isset($_REQUEST['search']))
$search = preg_replace('/[\'"\{\}\[\]\(\)]/', ' ', strip_tags($_REQUEST['search']));
// convert from unicode to utf8
$search = utf8::from_unicode($search);
// ensure we are really looking for something
if(preg_match('/^(chercher|search)/i', $search))
$search = '';
// search is constrained to only one section
$section_id = '';
if(isset($_REQUEST['anchor']) && (strpos($_REQUEST['anchor'], 'section:') === 0))
$section_id = str_replace('section:', '', $_REQUEST['anchor']);
$section_id = strip_tags($section_id);
// which page should be displayed
if(isset($_REQUEST['page']))
$page = $_REQUEST['page'];
else
$page = 1;
$page = max(1,intval($page));
// minimum size for any search token - depends of mySQL setup
$query = "SHOW VARIABLES LIKE 'ft_min_word_len'";
if(!defined('MINIMUM_TOKEN_SIZE') && ($row =& SQL::query_first($query)) && ($row['Value'] > 0))
define('MINIMUM_TOKEN_SIZE', $row['Value']);
// by default MySQL indexes words with at least four chars
if(!defined('MINIMUM_TOKEN_SIZE'))
define('MINIMUM_TOKEN_SIZE', 4);
// kill short and redundant tokens
$tokens = preg_split('/[\s,]+/', $search);
if(@count($tokens)) {
$search = '';
foreach($tokens as $token) {
// too short
if(strlen(preg_replace('/&.+?;/', 'x', $token)) < MINIMUM_TOKEN_SIZE)
continue;
// already here (repeated word)
if(strpos($search, $token) !== FALSE)
continue;
// keep this token
$search .= $token.' ';
}
$search = trim($search);
}
// load localized strings
i18n::bind('root');
// load the skin
load_skin('search');
// the title of the page
if($search)
$context['page_title'] = sprintf(i18n::s('Search: %s'), $search);
else
$context['page_title'] = i18n::s('Search');
// the form to submit a new search
$context['text'] .= '<form method="get" action="'.$context['script_url'].'" onsubmit="return validateDocumentPost(this)" id="main_form"><div>';
$fields = array();
// a field to type keywords
$label = i18n::s('You are searching for');
$input = '<input type="text" name="search" id="search" size="45" value="'.encode_field($search).'" maxlength="255" />';
$hint = i18n::s('Type one or several words.');
$fields[] = array($label, $input, $hint);
// limit the search to one section
$label = i18n::s('Search in');
if($section_id)
$current = 'section:'.$section_id;
else
$current = 'none';
$input = '<select name="anchor">'.'<option value="">'.i18n::s('-- All sections')."</option>\n".Sections::get_options($current, 'no_subsections').'</select>';
$hint = i18n::s('Look in all or only one section.');
$fields[] = array($label, $input, $hint);
// build the form
$context['text'] .= Skin::build_form($fields);
$fields = array();
// the submit button
$context['text'] .= '<p>'.Skin::build_submit_button(i18n::s('Submit'), i18n::s('Press [s] to submit data'), 's').'</p>'."\n";
// the form to submit a new search
$context['text'] .= '</div></form>';
// the script used for form handling at the browser
$context['text'] .= JS_PREFIX
.' // check that main fields are not empty'."\n"
.' func'.'tion validateDocumentPost(container) {'."\n"
."\n"
.' // search is mandatory'."\n"
.' if(!container.search.value) {'."\n"
.' alert("'.i18n::s('Please type something to search for').'");'."\n"
.' Yacs.stopWorking();'."\n"
.' return false;'."\n"
.' }'."\n"
."\n"
.' // successful check'."\n"
.' return true;'."\n"
.' }'."\n"
."\n"
.'// set the focus on first form field'."\n"
.'$("search").focus();'."\n"
.JS_SUFFIX."\n";
// nothing found yet
$no_result = TRUE;
// provide results in separate panels
$panels = array();
// search in sections
if($rows = Sections::search_in_section($section_id, $search)) {
$panels[] = array('sections', i18n::s('Sections'), 'sections_panel', Skin::build_list($rows, 'decorated'));
$no_result = FALSE;
}
// search in articles
$box = array();
$box['title'] = '';
$box['text'] = '';
$offset = ($page - 1) * ARTICLES_PER_PAGE;
$cap = 0;
if($items = Articles::search_in_section($section_id, $search, $offset, ARTICLES_PER_PAGE + 1)) {
$box['title'] = i18n::s('Matching articles');
// link to next page if greater than ARTICLES_PER_PAGE
$cap = count($items);
// limit the number of boxes displayed
if($cap > ARTICLES_PER_PAGE)
@array_splice($items, ARTICLES_PER_PAGE);
}
$cap += $offset;
// we have found some articles
if($cap || ($page > 1))
$no_result = FALSE;
// navigation commands for articles
$box['bar'] = array();
if($cap > ARTICLES_PER_PAGE)
$box['bar'] = array('_count' => i18n::s('Results'));
elseif($cap)
$box['bar'] = array('_count' => sprintf(i18n::ns('%d result', '%d results', count($items)), count($items)));
$home = 'search.php?search='.urlencode($search);
$prefix = $home.'&page=';
if(($navigate = Skin::navigate($home, $prefix, $cap, ARTICLES_PER_PAGE, $page)) && @count($navigate))
$box['bar'] += $navigate;
// actually render the html
if(@count($box['bar']))
$box['text'] .= Skin::build_list($box['bar'], 'menu_bar');
if(@count($items))
$box['text'] .= Skin::build_list($items, 'decorated');
elseif(is_string($items))
$box['text'] .= $items;
if($box['text'])
$panels[] = array('articles', i18n::s('Pages'), 'articles_panel', $box['text']);
// on first page, and if search is not constrained
if(($page == 1) && !$section_id) {
// search in files
if($rows = Files::search($search)) {
$panels[] = array('files', i18n::s('Files'), 'files_panel', Skin::build_list($rows, 'decorated'));
$no_result = FALSE;
}
// search in users
if($rows = Users::search($search)) {
$panels[] = array('users', i18n::s('People'), 'users_panel', Skin::build_list($rows, 'decorated'));
$no_result = FALSE;
}
// search in categories
if($rows = Categories::search($search)) {
$panels[] = array('categories', i18n::s('Categories'), 'categories_panel', Skin::build_list($rows, 'decorated'));
$no_result = FALSE;
}
}
// add an extra panel
if(isset($context['skins_delegate_search']) && ($context['skins_delegate_search'] == 'X')) {
$text = '';
// typically, a form and a button to search at another place
if(isset($context['skins_search_extension']) && $context['skins_search_extension'])
$text .= str_replace('%s', encode_field($search), $context['skins_search_extension']);
// look at other search engines
$text .= '<p style="margin: 2em 0 0 0">'.i18n::s('Search all of the Internet').'</p><ul>';
// encode for urls, but preserve unicode chars
$search = urlencode($search);
// Google
$link = 'http://www.google.com/search?q='.$search.'&ie=utf-8';
$text .= '<li>'.Skin::build_link($link, i18n::s('Google'), 'external').'</li>';
// Bing
$link = 'http://www.bing.com/search?q='.$search;
$text .= '<li>'.Skin::build_link($link, i18n::s('Bing'), 'external').'</li>';
// Yahoo!
$link = 'http://search.yahoo.com/search?p='.$search.'&ei=utf-8';
$text .= '<li>'.Skin::build_link($link, i18n::s('Yahoo!'), 'external').'</li>';
// Ask Jeeves
$link = 'http://web.ask.com/web?q='.$search;
$text .= '<li>'.Skin::build_link($link, i18n::s('Ask Jeeves'), 'external').'</li>';
// in a separate panel
$panels[] = array('extension', i18n::s('Extended search'), 'extensions_panel', $text);
$no_result = FALSE;
}
// assemble all tabs
if(count($panels))
$context['text'] .= Skin::build_tabs($panels);
// nothing found
if(!count($panels) && $search)
$context['text'] .= sprintf(i18n::s('<p>No page has been found. This will happen with very short words (less than %d letters), that are not fully indexed. This can happen as well if more than half of pages contain the searched words. Try to use most restrictive words and to suppress "noise" words.</p>'), MINIMUM_TOKEN_SIZE)."\n";
// search at peering sites, but only on unconstrained request and on first page
include_once $context['path_to_root'].'servers/servers.php';
if(!$section_id && ($page == 1) && ($servers = Servers::list_for_search(0, 3, 'search'))) {
// everything in a separate section
$context['text'] .= Skin::build_block(i18n::s('At partner sites'), 'title');
// query each server
foreach($servers as $server_url => $attributes) {
list($server_search, $server_label) = $attributes;
// a REST API that returns a RSS list
$result = Call::list_resources($server_search, array('search' => $search));
// error message
if(!$result[0])
$context['text'] .= $result[1];
// some results
else {
$items = array();
foreach($result[1] as $item) {
$suffix = '';
if($item['description'])
$suffix .= ' - '.$item['description'];
$suffix .= BR;
$details = array();
if($item['pubDate'])
$details[] = $item['pubDate'];
if($server_url)
$details[] = Skin::build_link($server_url, $server_label, 'server');
if(count($details))
$suffix .= '<span class="details">'.join(' - ', $details).'</span>';
$items[$item['link']] = array('', $item['title'], $suffix, 'external', '');
}
$context['text'] .= Skin::build_list($items, 'decorated');
}
}
}
//
// extra panel
//
// extend the search, but only at first page
if($search && ($page == 1)) {
// a tool to update the related category
if($cap && Surfer::is_member())
$context['page_tools'][] = Skin::build_link('categories/set_keyword.php?search='.urlencode($search), i18n::s('Remember this search'));
// same keywords on whole site
if($section_id)
$context['page_tools'][] = Skin::build_link('search.php?search='.urlencode($search), i18n::s('Search in all sections'), 'basic');
// submit one token to our page locator
if(preg_match('/^([\S-]+)/', $search, $matches))
$context['page_tools'][] = Skin::build_link(normalize_shortcut($matches[1]), i18n::s('Look for a named page'), 'basic');
}
// general help on this page
$context['components']['boxes'] = Skin::build_box(i18n::s('Help'), i18n::s('This search engine only display pages that have all words in it. <p>Also, only exact matches will be listed. Therefore "category" and "categories" won\'t give the same results. Note that "red" and "reds" may also give different results.</p>'), 'boxes', 'help');
// how to stay tuned
$lines = array();
if($search)
$lines[] = Skin::build_link('services/search.php?search='.urlencode($search), i18n::s('Matching pages'), 'xml');
$context['components']['boxes'] .= Skin::build_box(i18n::s('Monitor'), join(BR, $lines), 'boxes', 'feeds');
// side bar with the list of most recent keywords
$cache_id = 'search.php#keywords_by_date';
if(!$text =& Cache::get($cache_id)) {
if($items = Categories::list_keywords_by_date(0, COMPACT_LIST_SIZE))
$text =& Skin::build_box(i18n::s('Recent searches'), Skin::build_list($items, 'compact'), 'boxes');
Cache::put($cache_id, $text, 'categories');
}
$context['components']['boxes'] .= $text;
// render the skin
render_skin();
?>