Mercurial > hg > rc1
comparison plugins/advanced_search/advanced_search.php @ 34:50ac5484d514
one fix to distro
author | Charlie Root |
---|---|
date | Sun, 27 May 2018 16:53:56 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
33:d41c01c5c933 | 34:50ac5484d514 |
---|---|
1 <?php | |
2 /** | |
3 * Processing an advanced search over an E-Mail Account | |
4 * | |
5 * @version 2.1.5 | |
6 * @licence GNU GPLv3+ | |
7 * @author Wilwert Claude | |
8 * @author Ludovicy Steve | |
9 * @author Chris Moules | |
10 * @website http://www.gms.lu | |
11 * | |
12 * Updated by Alex S Grebenschikov (www.poralix.com) | |
13 * to make it compatible with RoundCube 1.3.0 | |
14 * Last modified: Tue Aug 1 06:41:46 CEST 2017 | |
15 * | |
16 */ | |
17 | |
18 class advanced_search extends rcube_plugin | |
19 { | |
20 | |
21 /** | |
22 * Instance of rcmail | |
23 * | |
24 * @var object | |
25 * @access private | |
26 */ | |
27 private $rc; | |
28 | |
29 public $task = 'mail|settings'; | |
30 | |
31 /** | |
32 * Plugin config | |
33 * | |
34 * @var array | |
35 * @access private | |
36 */ | |
37 private $config; | |
38 | |
39 /** | |
40 * Localization strings | |
41 * | |
42 * @var array | |
43 * @access private | |
44 */ | |
45 private $i18n_strings = array(); | |
46 | |
47 /** | |
48 * Initialisation of the plugin | |
49 * | |
50 * @access public | |
51 * @return null | |
52 */ | |
53 public function init() | |
54 { | |
55 $this->rc = rcmail::get_instance(); | |
56 $this->load_config("config-default.inc.php"); | |
57 $this->load_config(); | |
58 $this->config = $this->rc->config->get('advanced_search_plugin'); | |
59 $this->register_action('plugin.display_advanced_search', array($this, 'display_advanced_search')); | |
60 $this->register_action('plugin.trigger_search', array($this, 'trigger_search')); | |
61 $this->register_action('plugin.trigger_search_pagination', array($this, 'trigger_search_pagination')); | |
62 $this->register_action('plugin.save_search', array($this, 'save_search')); | |
63 $this->register_action('plugin.delete_search', array($this, 'delete_search')); | |
64 $this->register_action('plugin.get_saved_search', array($this, 'get_saved_search')); | |
65 | |
66 $this->skin = $this->rc->config->get('skin'); | |
67 $this->add_texts('localization', true); | |
68 $this->populate_i18n(); | |
69 if(isset($this->config['criteria'])) { | |
70 foreach($this->config['criteria'] as $key => $translation) { | |
71 $this->config['criteria'][$key] = $this->gettext($key); | |
72 } | |
73 } | |
74 $this->include_script('advanced_search.js'); | |
75 | |
76 if ($this->rc->task == 'mail') { | |
77 $file = 'skins/' . $this->skin . '/advanced_search.css'; | |
78 | |
79 if (file_exists($this->home . '/' . $file)) { | |
80 $this->include_stylesheet($file); | |
81 } else { | |
82 $this->include_stylesheet('skins/default/advanced_search.css'); | |
83 } | |
84 | |
85 if (empty($this->rc->action)) { | |
86 $this->add_menu_entry(); | |
87 } | |
88 } elseif ($this->rc->task == 'settings') { | |
89 $this->add_hook('preferences_list', array($this, 'preferences_list')); | |
90 $this->add_hook('preferences_save', array($this, 'preferences_save')); | |
91 $this->add_hook('preferences_sections_list', array($this, 'preferences_section')); | |
92 $file = 'skins/' . $this->skin . '/advanced_search.css'; | |
93 if (file_exists($this->home . '/' . $file)) { | |
94 $this->include_stylesheet($file); | |
95 } else { | |
96 $this->include_stylesheet('skins/default/advanced_search.css'); | |
97 } | |
98 } | |
99 | |
100 $this->add_hook('startup', array($this, 'startup')); | |
101 } | |
102 | |
103 | |
104 public function startup($args) | |
105 { | |
106 $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GET); | |
107 if (!isset($search)) { | |
108 $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_POST); | |
109 } | |
110 $rsearch = $search == 'advanced_search_active'; | |
111 $uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GET); | |
112 $draft_uid = rcube_utils::get_input_value('_draft_uid', rcube_utils::INPUT_GET); | |
113 $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GET); | |
114 $page = rcube_utils::get_input_value('_page', rcube_utils::INPUT_GET); | |
115 $sort = rcube_utils::get_input_value('_sort', rcube_utils::INPUT_GET); | |
116 | |
117 if (!empty($uid)) { | |
118 $parts = explode('__MB__', $uid); | |
119 if (count($parts) == 2) { | |
120 $search = 'advanced_search_active'; | |
121 } | |
122 } | |
123 if (!empty($draft_uid)) { | |
124 $parts = explode('__MB__', $draft_uid); | |
125 if (count($parts) == 2) { | |
126 $search = 'advanced_search_active'; | |
127 } | |
128 } | |
129 | |
130 if ($search == 'advanced_search_active') { | |
131 if ($args['action'] == 'show' && !empty($uid)) { | |
132 $parts = explode('__MB__', $uid); | |
133 $uid = $parts[0]; | |
134 $this->rc->output->redirect(array('_task' => 'mail', '_action' => $args['action'], '_mbox' => $mbox, '_uid' => $uid)); | |
135 } | |
136 if ($args['action'] == 'compose') { | |
137 $draft_uid = rcube_utils::get_input_value('_draft_uid', rcube_utils::INPUT_GET); | |
138 $parts = explode('__MB__', $draft_uid); | |
139 $draft_uid = $parts[0]; | |
140 if (!empty($draft_uid)) { | |
141 $this->rc->output->redirect(array('_task' => 'mail', '_action' => $args['action'], '_mbox' => $mbox, '_draft_uid' => $draft_uid)); | |
142 } | |
143 } | |
144 if ($args['action'] == 'list' && $rsearch) { | |
145 $this->rc->output->command('advanced_search_active', '_page=' . $page . '&_sort=' . $sort); | |
146 $this->rc->output->send(); | |
147 $args['abort'] = true; | |
148 } | |
149 if ($args['action'] == 'mark') { | |
150 $flag = rcube_utils::get_input_value('_flag', rcube_utils::INPUT_POST); | |
151 $uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST); | |
152 | |
153 $post_str = '_flag=' . $flag . '&_uid=' . $uid; | |
154 if ($quiet = rcube_utils::get_input_value('_quiet', rcube_utils::INPUT_POST)) { | |
155 $post_str .= '&_quiet=' . $quiet; | |
156 } | |
157 if ($from = rcube_utils::get_input_value('_from', rcube_utils::INPUT_POST)) { | |
158 $post_str .= '&_from=' . $from; | |
159 } | |
160 if ($count = rcube_utils::get_input_value('_count', rcube_utils::INPUT_POST)) { | |
161 $post_str .= '&_count=' . $count; | |
162 } | |
163 if ($ruid = rcube_utils::get_input_value('_ruid', rcube_utils::INPUT_POST)) { | |
164 $post_str .= '&_ruid=' . $ruid; | |
165 } | |
166 $this->rc->output->command('label_mark', $post_str); | |
167 $this->rc->output->send(); | |
168 $args['abort'] = true; | |
169 } | |
170 | |
171 } else { | |
172 if ($args['action'] != 'plugin.get_saved_search' && $args['action'] != 'plugin.save_search' && $args['action'] != 'plugin.delete_search') { | |
173 $this->rc->output->command('plugin.advanced_search_del_header', array()); | |
174 } | |
175 } | |
176 } | |
177 | |
178 /** | |
179 * This function populates an array with localization texts. | |
180 * This is needed as ew are using a lot of localizations from core. | |
181 * The core localizations are not avalable directly in JS | |
182 * | |
183 * @access private | |
184 * @return null | |
185 */ | |
186 private function populate_i18n() | |
187 { | |
188 $core = array('advsearch', 'search', 'resetsearch', 'addfield', 'delete', 'cancel'); | |
189 | |
190 foreach ($core as $label) { | |
191 $this->i18n_strings[$label] = $this->rc->gettext($label); | |
192 } | |
193 | |
194 $local = array('in', 'and', 'or', 'not', 'where', 'exclude', 'andsubfolders', 'allfolders', 'save_the_search', 'has_been_saved', 'deletesearch', 'has_been_deleted'); | |
195 | |
196 foreach ($local as $label) { | |
197 $this->i18n_strings[$label] = $this->gettext($label); | |
198 } | |
199 } | |
200 | |
201 /** | |
202 * This adds a button into the configured menu to use the advanced search | |
203 * | |
204 * @access public | |
205 * @return null | |
206 */ | |
207 public function add_menu_entry() | |
208 { | |
209 $displayOptions = $this->rc->config->get('advanced_search_display_options', array()); | |
210 $target_menu = (isset($displayOptions['target_menu']) && !empty($displayOptions['target_menu'])) ? $displayOptions['target_menu'] : $this->config['target_menu']; | |
211 if ($target_menu != 'toolbar') { | |
212 $this->api->add_content( | |
213 html::tag( | |
214 'li', | |
215 null, | |
216 $this->api->output->button( | |
217 array( | |
218 'command' => 'plugin.advanced_search', | |
219 'label' => 'advsearch', | |
220 'type' => 'link', | |
221 'classact' => 'icon advanced-search active', | |
222 'class' => 'icon advanced-search', | |
223 'innerclass' => 'icon advanced-search' | |
224 ) | |
225 ) | |
226 ), | |
227 $target_menu | |
228 ); | |
229 } else { | |
230 $this->api->add_content( | |
231 $this->api->output->button( | |
232 array( | |
233 'command' => 'plugin.advanced_search', | |
234 'title' => 'advsearch', | |
235 'label' => 'search', | |
236 'type' => 'link', | |
237 'classact' => 'button advanced-search active', | |
238 'class' => 'button advanced-search', | |
239 'innerclass' => 'button advanced-search', | |
240 ) | |
241 ), | |
242 $target_menu | |
243 ); | |
244 } | |
245 } | |
246 | |
247 /** | |
248 * This function quotes some specific values based on their data type | |
249 * | |
250 * @param mixed $input The value to get quoted | |
251 * @access public | |
252 * @return The quoted value | |
253 */ | |
254 public function quote($value) | |
255 { | |
256 if (getType($value) == 'string') { | |
257 if (!preg_match('/"/', $value)) { | |
258 $value = preg_replace('/^"/', '', $value); | |
259 $value = preg_replace('/"$/', '', $value); | |
260 $value = preg_replace('/"/', '\\"', $value); | |
261 } | |
262 | |
263 $value = '"' . $value . '"'; | |
264 } | |
265 | |
266 return $value; | |
267 } | |
268 | |
269 /** | |
270 * This function generates the IMAP compatible search query based on the request data (by javascript) | |
271 * | |
272 * @param array $input The raw criteria data sent by javascript | |
273 * @access private | |
274 * @return string or int | |
275 */ | |
276 private function process_search_part($search_part) | |
277 { | |
278 $command_str = ''; | |
279 $flag = false; | |
280 | |
281 // Check for valid input | |
282 if (!array_key_exists($search_part['filter'], $this->config['criteria'])) { | |
283 $this->rc->output->show_message($this->gettext('internalerror'), 'error'); | |
284 | |
285 return 0; | |
286 } | |
287 if (in_array($search_part['filter'], $this->config['flag_criteria'])) { | |
288 $flag = true; | |
289 } | |
290 if (!$flag && !(isset($search_part['filter-val']) && $search_part['filter-val'] != '')) { | |
291 return 1; | |
292 } | |
293 | |
294 // Negate part | |
295 if ($search_part['not'] == 'true') { | |
296 $command_str .= 'NOT '; | |
297 } | |
298 | |
299 $command_str .= $search_part['filter']; | |
300 | |
301 if (!$flag) { | |
302 if (in_array($search_part['filter'], $this->config['date_criteria'])) { | |
303 // Take date format from user environment | |
304 $date_format = $this->rc->config->get('date_format', 'Y-m-d'); | |
305 // Try to use PHP5.2+ DateTime but fallback to ugly old method | |
306 if (class_exists('DateTime')) { | |
307 $date = DateTime::createFromFormat($date_format, $search_part['filter-val']); | |
308 $command_str .= ' ' . $this->quote($date->format("d-M-Y")); | |
309 } else { | |
310 $date_format = preg_replace('/(\w)/', '%$1', $date_format); | |
311 $date_array = strptime($search_part['filter-val'], $date_format); | |
312 $unix_ts = mktime($date_array['tm_hour'], $date_array['tm_min'], $date_array['tm_sec'], $date_array['tm_mon']+1, $date_array['tm_mday'], $date_array['tm_year']+1900); | |
313 $command_str .= ' ' . $this->quote(date("d-M-Y", $unix_ts)); | |
314 } | |
315 } elseif (in_array($search_part['filter'], $this->config['email_criteria'])) { | |
316 // Strip possible ',' added by autocomplete | |
317 $command_str .= ' ' . $this->quote(trim($search_part['filter-val'], " \t,")); | |
318 } else { | |
319 // Don't try to use a value for a binary flag object | |
320 $command_str .= ' ' . $this->quote($search_part['filter-val']); | |
321 } | |
322 } | |
323 | |
324 return $command_str; | |
325 } | |
326 | |
327 /** | |
328 * This function generates the IMAP compatible search query based on the request data (by javascript) | |
329 * | |
330 * @param array $input The raw criteria data sent by javascript | |
331 * @access public | |
332 * @return The final search command | |
333 */ | |
334 public function get_search_query($input) | |
335 { | |
336 $command = array(); | |
337 | |
338 foreach ($input as $search_part) { | |
339 // Skip excluded parts | |
340 if ($search_part['excluded'] == 'true') { | |
341 continue; | |
342 } | |
343 if (! $part_command = $this->process_search_part($search_part)) { | |
344 return 0; | |
345 } | |
346 // Skip invalid parts | |
347 if ($part_command === 1) { | |
348 continue; | |
349 } | |
350 | |
351 $command[] = array('method' => isset($search_part['method']) ? $search_part['method'] : 'and', | |
352 'command' => $part_command); | |
353 } | |
354 | |
355 $command_string = $this->build_search_string($command); | |
356 | |
357 return $command_string; | |
358 } | |
359 | |
360 /** | |
361 * This function converts the preconfigured query parts (as array) into an IMAP compatible string | |
362 * | |
363 * @param array $command_array An array containing the advanced search criteria | |
364 * @access public | |
365 * @return The command string | |
366 */ | |
367 private function build_search_string($command_array) | |
368 { | |
369 $command = array(); | |
370 $paranthesis = 0; | |
371 $prev_method = null; | |
372 $next_method = null; | |
373 $cnt = count($command_array); | |
374 | |
375 foreach ($command_array as $k => $v) { | |
376 $part = ''; | |
377 $next_method = 'unknown'; | |
378 | |
379 // Lookup next method | |
380 if ($k < $cnt-1) { | |
381 $next_method = $command_array[$k+1]['method']; | |
382 } | |
383 | |
384 // If previous option was OR, close any open brakets | |
385 if ($paranthesis > 0 && $prev_method == 'or' && $v['method'] != 'or') { | |
386 for (; $paranthesis > 0; $paranthesis--) { | |
387 $part .= ')'; | |
388 } | |
389 } | |
390 | |
391 // If there are two consecutive ORs, add brakets | |
392 // If the next option is a new OR, add the prefix here | |
393 // If the next option is _not_ an OR, and the current option is AND, prefix ALL | |
394 if ($next_method == 'or') { | |
395 if ($v['method'] == 'or') { | |
396 $part .= ' ('; | |
397 $paranthesis++; | |
398 } | |
399 $part .= 'OR '; | |
400 } elseif ($v['method'] == 'and') { | |
401 $part .= 'ALL '; | |
402 } | |
403 | |
404 $part .= $v['command']; | |
405 | |
406 // If this is the end of the query, and we have open brakets, close them | |
407 if ($k == $cnt-1 && $paranthesis > 0) { | |
408 for (; $paranthesis > 0; $paranthesis--) { | |
409 $part .= ')'; | |
410 } | |
411 } | |
412 | |
413 $prev_method = $v['method']; | |
414 $command[] = $part; | |
415 } | |
416 | |
417 $command = implode(' ', $command); | |
418 | |
419 return $command; | |
420 } | |
421 | |
422 /** | |
423 * This functions sends the initial data to the client side where a form (in dialog) is built for the advanced search | |
424 * | |
425 * @access public | |
426 * @return null | |
427 */ | |
428 public function display_advanced_search() | |
429 { | |
430 $ret = array('html' => $this->generate_searchbox(), | |
431 'row' => $this->add_row(), | |
432 'saved_searches' => $this->get_saved_search_names(), | |
433 'title' => $this->i18n_strings['advsearch'], | |
434 'date_criteria' => $this->config['date_criteria'], | |
435 'flag_criteria' => $this->config['flag_criteria'], | |
436 'email_criteria' => $this->config['email_criteria']); | |
437 | |
438 $this->rc->output->command('plugin.show', $ret); | |
439 } | |
440 | |
441 public function generate_searchbox() | |
442 { | |
443 $search_button = new html_inputfield(array('type' => 'submit', 'name' => 'search', 'class' => 'button mainaction', 'value' => $this->i18n_strings['search'])); | |
444 $reset_button = new html_inputfield(array('type' => 'reset', 'name' => 'reset', 'class' => 'button reset', 'value' => $this->i18n_strings['resetsearch'])); | |
445 $save_button = html::tag('input', array('type' => 'submit', 'name' => 'save_the_search', id=> 'save_the_search', 'class' => 'button save_search', 'value' => $this->i18n_strings['save_the_search'])); | |
446 $delete_button = new html_inputfield(array('type' => 'submit', 'name' => 'delete', 'style' => 'display: none;', 'class' => 'button delete_search', 'value' => $this->i18n_strings['deletesearch'])); | |
447 | |
448 $layout_table = new html_table(); | |
449 $layout_table->add(null, $search_button->show()); | |
450 $folderConfig = array('name' => 'folder'); | |
451 $layout_table->add( | |
452 null, | |
453 $this->i18n_strings['in'] . ': ' . | |
454 $this->folder_selector($folderConfig)->show($this->rc->storage->get_folder()) . | |
455 html::span( | |
456 array('class' => 'sub-folders'), | |
457 $this->i18n_strings['andsubfolders'] . ': ' . | |
458 html::tag( | |
459 'input', | |
460 array('type' => 'checkbox', 'name' => 'subfolder'), | |
461 null | |
462 ) | |
463 ) . | |
464 $this->i19n_strings['where'] | |
465 ); | |
466 $first_row = $this->add_row(true); | |
467 $layout_table->add_row(); | |
468 $layout_table->add(array('class' => 'adv-search-and-or'), null); | |
469 $layout_table->add(null, $first_row); | |
470 $layout_table->add_row(); | |
471 $layout_table->add(null, $search_button->show()); | |
472 $layout_table->add(null, $save_button . ' ' . $reset_button->show() . ' ' . $delete_button->show()); | |
473 | |
474 return html::tag( | |
475 'div', | |
476 array('id' => 'adsearch-popup'), | |
477 html::tag( | |
478 'form', | |
479 array('method' => 'post', 'action' => '#'), | |
480 $layout_table->show() | |
481 ) | |
482 ); | |
483 } | |
484 | |
485 /** | |
486 * This function is used to render the html of the advanced search form and also | |
487 * the later following rows are created by this function | |
488 * | |
489 * @param array $folders Array of folders | |
490 * @param boolean $first True if form gets created, False to create a new row | |
491 * @access public | |
492 * @return string The final html | |
493 */ | |
494 public function add_row($first = false) | |
495 { | |
496 $row_html = ''; | |
497 $optgroups = ''; | |
498 | |
499 $criteria = $this->config['criteria']; | |
500 $all_criteria = array( | |
501 $this->gettext('Common') => $this->config['prefered_criteria'], | |
502 $this->gettext('Addresses') => $this->config['email_criteria'], | |
503 $this->gettext('Dates') => $this->config['date_criteria'], | |
504 $this->gettext('Flags') => $this->config['flag_criteria'], | |
505 $this->gettext('Other') => $this->config['other_criteria'], | |
506 ); | |
507 | |
508 foreach ($all_criteria as $label => $specific_criteria) { | |
509 $options = ''; | |
510 | |
511 foreach ($specific_criteria as $value) { | |
512 $options .= html::tag('option', array('value' => $value), $criteria[$value]); | |
513 } | |
514 | |
515 $optgroups .= html::tag('optgroup', array('label' => $label), $options); | |
516 } | |
517 | |
518 $tmp = html::tag('select', array('name' => 'filter'), $optgroups); | |
519 $tmp .= $this->i18n_strings['not'] . ':' . html::tag('input', array('type' => 'checkbox', 'name' => 'not'), null); | |
520 $tmp .= html::tag('input', array('type' => 'text', 'name' => 'filter-val')); | |
521 $tmp .= $this->i18n_strings['exclude'] . ':' . html::tag('input', array('type' => 'checkbox', 'name' => 'filter-exclude'), null); | |
522 $tmp .= html::tag('button', array('name' => 'add', 'class' => 'add'), $this->i18n_strings['addfield']); | |
523 | |
524 if ($first) { | |
525 $row_html = $tmp; | |
526 } else { | |
527 $and_or_select = new html_select(array('name' => 'method')); | |
528 $and_or_select->add($this->i18n_strings['and'], 'and'); | |
529 $and_or_select->add($this->i18n_strings['or'], 'or'); | |
530 $tmp .= html::tag('button', array('name' => 'delete', 'class' => 'delete'), $this->i18n_strings['delete']); | |
531 $row_html = html::tag( | |
532 'tr', | |
533 null, | |
534 html::tag( | |
535 'td', | |
536 array('class' => 'adv-search-and-or'), | |
537 $and_or_select->show() | |
538 ) . | |
539 html::tag( | |
540 'td', | |
541 null, | |
542 $tmp | |
543 ) | |
544 ); | |
545 } | |
546 | |
547 return $row_html; | |
548 } | |
549 | |
550 /** | |
551 * Return folders list as html_select object | |
552 * | |
553 * This is a copy of the core function and adapted to fit | |
554 * the needs of the advanced_search function | |
555 * | |
556 * @param array $p Named parameters | |
557 * | |
558 * @return html_select HTML drop-down object | |
559 */ | |
560 public function folder_selector($p = array()) | |
561 { | |
562 $p += array('maxlength' => 100, 'realnames' => false, 'is_escaped' => true); | |
563 $a_mailboxes = array(); | |
564 $storage = $this->rc->get_storage(); | |
565 | |
566 $list = $storage->list_folders_subscribed(); | |
567 $delimiter = $storage->get_hierarchy_delimiter(); | |
568 | |
569 foreach ($list as $folder) { | |
570 $this->rc->build_folder_tree($a_mailboxes, $folder, $delimiter); | |
571 } | |
572 | |
573 $select = new html_select($p); | |
574 $select->add($this->i18n_strings['allfolders'], "all"); | |
575 | |
576 $this->rc->render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p); | |
577 | |
578 return $select; | |
579 } | |
580 | |
581 public function trigger_search_pagination($param) | |
582 { | |
583 $_GET['search'] = $_SESSION['av_search']; | |
584 $_GET['folder'] = $_SESSION['av_folder']; | |
585 $_GET['sub_folders'] = $_SESSION['av_sub_folders']; | |
586 $this->trigger_search(true); | |
587 } | |
588 | |
589 /** | |
590 * Here is where the actual query is fired to the imap server and the result is evaluated and sent back to the client side | |
591 * | |
592 * @access public | |
593 * @return null | |
594 */ | |
595 public function trigger_search($inPagination = false) | |
596 { | |
597 $search = rcube_utils::get_input_value('search', rcube_utils::INPUT_GPC); | |
598 // reset list_page and old search results | |
599 $this->rc->storage->set_page(1); | |
600 $this->rc->storage->set_search_set(null); | |
601 $page = rcube_utils::get_input_value('_page', rcube_utils::INPUT_GPC); | |
602 $page = $page ? $page : 1; | |
603 $pagesize = $this->rc->storage->get_pagesize(); | |
604 | |
605 if (!empty($search)) { | |
606 $mbox = rcube_utils::get_input_value('folder', rcube_utils::INPUT_GPC); | |
607 $imap_charset = RCMAIL_CHARSET; | |
608 $sort_column = rcmail_sort_column(); | |
609 $search_str = $this->get_search_query($search); | |
610 $sub_folders = rcube_utils::get_input_value('sub_folders', rcube_utils::INPUT_GPC) == 'true'; | |
611 $folders = array(); | |
612 $result_h = array(); | |
613 $count = 0; | |
614 $new_id = 1; | |
615 $current_mbox = $this->rc->storage->get_folder(); | |
616 $uid_list = array(); | |
617 //Store information in session for pagination | |
618 $_SESSION['av_search'] = $search; | |
619 $_SESSION['av_folder'] = $mbox; | |
620 $_SESSION['av_sub_folders'] = rcube_utils::get_input_value('sub_folders', rcube_utils::INPUT_GPC); | |
621 $nosub = $sub_folders; | |
622 $folders = $this->rc->get_storage()->list_folders_subscribed(); | |
623 if (empty($folders) || ($sub_folders === false && $mbox !== 'all')) { | |
624 $folders = array($mbox); | |
625 } elseif ($mbox !== 'all') { | |
626 if ($sub_folders === false) { | |
627 $folders = array($mbox); | |
628 } else { | |
629 $folders = $this->rc->get_storage()->list_folders_subscribed_direct($mbox); | |
630 } | |
631 } | |
632 $md5folders = array(); | |
633 foreach ($folders as $folder) { | |
634 $md5folders[md5($folder)] = $folder; | |
635 } | |
636 $this->rc->output->set_env('as_md5_folders', $md5folders); | |
637 | |
638 if ($search_str) { | |
639 $res = $this->perform_search($search_str, $folders, $page); | |
640 $count = $res['count']; | |
641 } | |
642 | |
643 | |
644 if ($count > 0) { | |
645 $_SESSION['advanced_search']['uid_list'] = $uid_list; | |
646 if ($search_str && $inPagination == false) { | |
647 $this->rc->output->show_message('searchsuccessful', 'confirmation', array('nr' => $count)); | |
648 } | |
649 } elseif ($err_code = $this->rc->storage->get_error_code()) { | |
650 rcmail_display_server_error(); | |
651 } else { | |
652 $this->rc->output->show_message('searchnomatch', 'notice'); | |
653 } | |
654 | |
655 $current_folder = rcube_utils::get_input_value('current_folder', rcube_utils::INPUT_GPC); | |
656 | |
657 $this->rc->output->set_env('search_request', 'advanced_search_active'); | |
658 $this->rc->output->set_env('messagecount', $count); | |
659 $this->rc->output->set_env('pagecount', ceil($count / $pagesize)); | |
660 $this->rc->output->set_env('exists', $this->rc->storage->count($current_folder, 'EXISTS')); | |
661 $this->rc->output->command('set_rowcount', rcmail_get_messagecount_text($count, $page)); | |
662 $this->rc->output->command('plugin.search_complete'); | |
663 $this->rc->output->send(); | |
664 } | |
665 } | |
666 | |
667 /** | |
668 * return javascript commands to add rows to the message list | |
669 */ | |
670 public function rcmail_js_message_list($a_headers, $insert_top = false, $a_show_cols = null, $avmbox = false, $avcols = array(), $showMboxColumn = false) | |
671 { | |
672 global $CONFIG, $RCMAIL, $OUTPUT; | |
673 $uid_mboxes = array(); | |
674 | |
675 if (empty($a_show_cols)) { | |
676 if (!empty($_SESSION['list_attrib']['columns'])) { | |
677 $a_show_cols = $_SESSION['list_attrib']['columns']; | |
678 } else { | |
679 $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); | |
680 } | |
681 } else { | |
682 if (!is_array($a_show_cols)) { | |
683 $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($a_show_cols)); | |
684 } | |
685 $head_replace = true; | |
686 } | |
687 | |
688 $mbox = $RCMAIL->storage->get_folder(); | |
689 | |
690 // make sure 'threads' and 'subject' columns are present | |
691 if (!in_array('subject', $a_show_cols)) { | |
692 array_unshift($a_show_cols, 'subject'); | |
693 } | |
694 if (!in_array('threads', $a_show_cols)) { | |
695 array_unshift($a_show_cols, 'threads'); | |
696 } | |
697 $_SESSION['list_attrib']['columns'] = $a_show_cols; | |
698 | |
699 // Make sure there are no duplicated columns (#1486999) | |
700 $a_show_cols = array_merge($a_show_cols, $avcols); | |
701 $a_show_cols = array_unique($a_show_cols); | |
702 | |
703 // Plugins may set header's list_cols/list_flags and other rcube_message_header variables | |
704 // and list columns | |
705 $plugin = $RCMAIL->plugins->exec_hook( | |
706 'messages_list', | |
707 array('messages' => $a_headers, 'cols' => $a_show_cols) | |
708 ); | |
709 $a_show_cols = $plugin['cols']; | |
710 $a_headers = $plugin['messages']; | |
711 $thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : null; | |
712 | |
713 // get name of smart From/To column in folder context | |
714 if (($f = array_search('fromto', $a_show_cols)) !== false) { | |
715 $smart_col = rcmail_message_list_smart_column_name(); | |
716 } | |
717 if ($this->coltypesSet == false) { | |
718 $OUTPUT->command('set_message_coltypes', $a_show_cols, $thead, $smart_col); | |
719 if ($showMboxColumn === true) { | |
720 $OUTPUT->command('plugin.advanced_search_add_header', array()); | |
721 } | |
722 $this->coltypesSet = true; | |
723 } | |
724 | |
725 if (empty($a_headers)) { | |
726 return; | |
727 } | |
728 | |
729 // remove 'threads', 'attachment', 'flag', 'status' columns, we don't need them here | |
730 foreach (array('threads', 'attachment', 'flag', 'status', 'priority') as $col) { | |
731 if (($key = array_search($col, $a_show_cols)) !== false) { | |
732 unset($a_show_cols[$key]); | |
733 } | |
734 } | |
735 | |
736 // loop through message headers | |
737 foreach ($a_headers as $n => $header) { | |
738 if (empty($header)) { | |
739 continue; | |
740 } | |
741 $a_msg_cols = array(); | |
742 $a_msg_flags = array(); | |
743 // format each col; similar as in rcmail_message_list() | |
744 foreach ($a_show_cols as $col) { | |
745 $col_name = $col == 'fromto' ? $smart_col : $col; | |
746 | |
747 if (in_array($col_name, array('from', 'to', 'cc', 'replyto'))) { | |
748 $cont = rcmail_address_string($header->$col_name, 3, false, null, $header->charset); | |
749 } elseif ($col == 'subject') { | |
750 $cont = trim(rcube_mime::decode_header($header->$col, $header->charset)); | |
751 if (!$cont) { | |
752 $cont = $this->gettext('nosubject'); | |
753 } | |
754 $cont = rcube::Q($cont); | |
755 } elseif ($col == 'size') { | |
756 $cont = $this->rc->show_bytes($header->$col); | |
757 } elseif ($col == 'date') { | |
758 $cont = rcube_utils::format_datestr($header->date,$date_format); | |
759 } else { | |
760 $cont = rcube::Q($header->$col); | |
761 } | |
762 $a_msg_cols[$col] = $cont; | |
763 } | |
764 | |
765 $a_msg_flags = array_change_key_case(array_map('intval', (array) $header->flags)); | |
766 if ($header->depth) { | |
767 $a_msg_flags['depth'] = $header->depth; | |
768 } elseif ($header->has_children) { | |
769 $roots[] = $header->uid; | |
770 } | |
771 if ($header->parent_uid) { | |
772 $a_msg_flags['parent_uid'] = $header->parent_uid; | |
773 } | |
774 if ($header->has_children) { | |
775 $a_msg_flags['has_children'] = $header->has_children; | |
776 } | |
777 if ($header->unread_children) { | |
778 $a_msg_flags['unread_children'] = $header->unread_children; | |
779 } | |
780 if ($header->others['list-post']) { | |
781 $a_msg_flags['ml'] = 1; | |
782 } | |
783 if ($header->priority) { | |
784 $a_msg_flags['prio'] = (int) $header->priority; | |
785 } | |
786 $a_msg_flags['ctype'] = rcube::Q($header->ctype); | |
787 $a_msg_flags['mbox'] = $mbox; | |
788 if (!empty($header->list_flags) && is_array($header->list_flags)) { | |
789 $a_msg_flags = array_merge($a_msg_flags, $header->list_flags); | |
790 } | |
791 if (!empty($header->list_cols) && is_array($header->list_cols)) { | |
792 $a_msg_cols = array_merge($a_msg_cols, $header->list_cols); | |
793 } | |
794 if ($showMboxColumn === true) { | |
795 $a_msg_flags['avmbox'] = $avmbox; | |
796 } | |
797 | |
798 $OUTPUT->command( | |
799 'add_message_row', | |
800 $header->uid . '__MB__' . md5($mbox), | |
801 $a_msg_cols, | |
802 $a_msg_flags, | |
803 $insert_top | |
804 ); | |
805 $id = $header->uid . '__MB__' . md5($mbox); | |
806 $uid_mboxes[$id] = array('uid' => $header->uid, 'mbox' => $mbox, 'md5mbox' => md5($mbox)); | |
807 } | |
808 | |
809 if ($RCMAIL->storage->get_threading()) { | |
810 $OUTPUT->command('init_threads', (array) $roots, $mbox); | |
811 } | |
812 | |
813 return $uid_mboxes; | |
814 } | |
815 | |
816 | |
817 private function do_pagination($folders, $onPage) | |
818 { | |
819 $perPage = $this->rc->storage->get_pagesize(); | |
820 $from = $perPage * $onPage - $perPage + 1; | |
821 $to = $from + $perPage - 1; | |
822 $got = 0; | |
823 $pos = 0; | |
824 $cbox = ""; | |
825 $boxStart = 0; | |
826 $boxStop = 0; | |
827 $fetch = array(); | |
828 foreach ($folders as $box => $num) { | |
829 $i = $num; | |
830 if ($box != $cbox) { | |
831 $boxStart = 0; | |
832 $boxStop = 0; | |
833 $cbox = $box; | |
834 } | |
835 while ($i--) { | |
836 $pos++; | |
837 $boxStart++; | |
838 if ($pos >= $from && $pos <= $to) { | |
839 if (!isset($fetch[$box])) { | |
840 $fetch[$box] = array("from" => $boxStart); | |
841 } | |
842 $fetch[$box]['to'] = $boxStart; | |
843 $got++; | |
844 } | |
845 } | |
846 if ($got >= $perPage) { | |
847 break; | |
848 } | |
849 } | |
850 | |
851 return $fetch; | |
852 } | |
853 | |
854 /** | |
855 * Save advanced search preferences | |
856 * | |
857 * @access public | |
858 */ | |
859 public function preferences_save($args) | |
860 { | |
861 if ($args['section'] != 'advancedsearch') { | |
862 return; | |
863 } | |
864 $rcmail = rcmail::get_instance(); | |
865 | |
866 $displayOptions = array(); | |
867 $displayOptions['_show_message_label_header'] = rcube_utils::get_input_value('_show_message_label_header', rcube_utils::INPUT_POST) == 1 ? true : false; | |
868 $displayOptions['_show_message_mbox_info'] = rcube_utils::get_input_value('_show_message_mbox_info', rcube_utils::INPUT_POST) == 1 ? true : false; | |
869 $displayOptions['target_menu'] = rcube_utils::get_input_value('button_display_option', rcube_utils::INPUT_POST); | |
870 | |
871 $args['prefs']['advanced_search_display_options'] = $displayOptions; | |
872 | |
873 return($args); | |
874 } | |
875 | |
876 /** | |
877 * Add a section advanced search to the preferences section list | |
878 * | |
879 * @access public | |
880 */ | |
881 public function preferences_section($args) | |
882 { | |
883 $args['list']['advancedsearch'] = array( | |
884 'id' => 'advancedsearch', | |
885 'section' => rcube::Q($this->gettext('advancedsearch')) | |
886 ); | |
887 | |
888 return($args); | |
889 } | |
890 | |
891 /** | |
892 * Display advanced search configuration in user preferences tab | |
893 * | |
894 * @access public | |
895 */ | |
896 public function preferences_list($args) | |
897 { | |
898 if ($args['section'] == 'advancedsearch') { | |
899 | |
900 $this->rc = rcmail::get_instance(); | |
901 $args['blocks']['label_display_options'] = array('options' => array(), 'name' => rcube::Q($this->gettext('label_display_options'))); | |
902 | |
903 $displayOptions = $this->rc->config->get('advanced_search_display_options', array()); | |
904 $target_menu = (isset($displayOptions['target_menu']) && !empty($displayOptions['target_menu'])) ? $displayOptions['target_menu'] : $this->config['target_menu']; | |
905 $options = ''; | |
906 $optarg = array('value' => 'messagemenu'); | |
907 if ($target_menu == 'messagemenu') { | |
908 $optarg['selected'] = 'selected'; | |
909 $target_image = 'menu_location_a.jpg'; | |
910 } | |
911 $options .= html::tag('option', $optarg, rcube::Q($this->gettext('display_in_messagemenu'))); | |
912 $optarg = array('value' => 'toolbar'); | |
913 if ($target_menu == 'toolbar') { | |
914 $optarg['selected'] = 'selected'; | |
915 $target_image = 'menu_location_b.jpg'; | |
916 } | |
917 $options .= html::tag('option', $optarg,rcube::Q($this->gettext('display_in_toolbar'))); | |
918 $select = html::tag('select', array('name' => 'button_display_option', 'id' => 'button_display_option'), $options); | |
919 | |
920 $label1 = html::label('_show_message_label_header', rcube::Q($this->gettext('mailbox_headers_in_results'))); | |
921 $label2 = html::label('_show_message_mbox_info', rcube::Q($this->gettext('mailbox_info_in_results'))); | |
922 $label3 = html::label('button_display_option', rcube::Q($this->gettext('show_advanced_search'))); | |
923 | |
924 $arg1 = array('name' => '_show_message_label_header', 'id' => '_show_message_label_header', 'type' => 'checkbox', 'title' => "", 'class' => 'watermark linput', 'value' => 1); | |
925 if (isset($displayOptions['_show_message_label_header']) && $displayOptions['_show_message_label_header'] === true) { | |
926 $arg1['checked'] = 'checked'; | |
927 $img1class = 'enabled'; | |
928 } else { | |
929 $img1class = 'disabled'; | |
930 } | |
931 $check1 = html::tag('input', $arg1); | |
932 $arg2 = array('name' => '_show_message_mbox_info', 'id' => '_show_message_mbox_info', 'type' => 'checkbox', 'title' => "", 'class' => 'watermark linput', 'value' => 1); | |
933 if (isset($displayOptions['_show_message_mbox_info']) && $displayOptions['_show_message_mbox_info'] === true) { | |
934 $arg2['checked'] = 'checked'; | |
935 $img2class = 'enabled'; | |
936 } else { | |
937 $img2class = 'disabled'; | |
938 } | |
939 | |
940 $img1 = html::img(array('src' => $this->url('skins/larry/images/show_mbox_row.jpg'), 'class' => $img1class)); | |
941 $img2 = html::img(array('src' => $this->url('skins/larry/images/show_mbox_col.jpg'), 'class' => $img2class)); | |
942 $img3 = html::img(array('src' => $this->url('skins/larry/images/' . $target_image))); | |
943 | |
944 $check2 = html::tag('input', $arg2); | |
945 $args['blocks']['label_display_options']['options'][0] = array('title' => '', 'content' => '<p class="avsearchpref"><span>' . $check1 . ' ' . $label1 . '</span> ' . $img1 . '</p>'); | |
946 $args['blocks']['label_display_options']['options'][1] = array('title' => '', 'content' => '<p class="avsearchpref"><span>' . $check2 . ' ' . $label2 . '</span> ' . $img2 . '</p>'); | |
947 $args['blocks']['label_display_options']['options'][2] = array('title' => '', 'content' => '<p class="avsearchpref"><span>' . $label3 . ' ' . $select . '</span> ' . $img3 . '</p>'); | |
948 } | |
949 | |
950 return($args); | |
951 } | |
952 | |
953 private function perform_search($search_string, $folders, $page = 1) | |
954 { | |
955 // Search all folders and build a final set | |
956 if ($folders[0] == 'all' || empty($folders)) { | |
957 $folders_search = $this->rc->imap->list_folders_subscribed(); | |
958 } else { | |
959 $folders_search = $folders; | |
960 } | |
961 $count = 0; | |
962 $folder_count = array(); | |
963 foreach ($folders_search as $mbox) { | |
964 $this->rc->storage->set_folder($mbox); | |
965 $this->rc->storage->search($mbox, $search_string, RCMAIL_CHARSET, $_SESSION['sort_col']); | |
966 $result = array(); | |
967 $fcount = $this->rc->storage->count($mbox, 'ALL', !empty($_REQUEST['_refresh'])); | |
968 $count += $fcount; | |
969 $folder_count[$mbox] = $fcount; | |
970 } | |
971 foreach ($folder_count as $k => $v) { | |
972 if ($v == 0) { | |
973 unset($folder_count[$k]); | |
974 } | |
975 } | |
976 $fetch = $this->do_pagination($folder_count, $page); | |
977 $mails = array(); | |
978 $currentMailbox = ""; | |
979 $displayOptions = $this->rc->config->get('advanced_search_display_options', array()); | |
980 $showMboxColumn = isset($displayOptions['_show_message_mbox_info']) && $displayOptions['_show_message_mbox_info'] ? true : false; | |
981 $uid_mboxes = array(); | |
982 foreach ($fetch as $mailbox => $data) { | |
983 if ($currentMailbox != $mailbox) { | |
984 $currentMailbox = $mailbox; | |
985 if (isset($displayOptions['_show_message_label_header']) && $displayOptions['_show_message_label_header'] === true) { | |
986 $this->rc->output->command('advanced_search_add_mbox', $mailbox, $folder_count[$mailbox], $showMboxColumn); | |
987 } | |
988 } | |
989 $uid_mboxes = array_merge($uid_mboxes, $this->getMails($mailbox, $data, $search_string, $showMboxColumn)); | |
990 } | |
991 | |
992 return array('result' => array(), 'count' => $count, 'uid_mboxes' => $uid_mboxes); | |
993 } | |
994 | |
995 private function getMails($mailbox, $data, $search_string, $showMboxColumn) | |
996 { | |
997 $pageSize = $this->rc->storage->get_pagesize(); | |
998 $msgNum = $data['from']; | |
999 $startPage = ceil($msgNum/$pageSize); | |
1000 $msgMod = $msgNum % $pageSize; | |
1001 $multiPage = "false"; | |
1002 $firstArrayElement = $msgMod == 0 ? ($pageSize-1) : ($msgMod-1); | |
1003 $quantity = $data['to'] - $data['from']; | |
1004 if ($data['from'] + $quantity > $pageSize) { | |
1005 $multiPage = "true"; | |
1006 } | |
1007 $this->rc->storage->set_folder($mailbox); | |
1008 $this->rc->storage->search($mailbox, $search_string, RCMAIL_CHARSET, $_SESSION['sort_col']); | |
1009 $messages = $this->rc->storage->list_messages('', $startPage); | |
1010 if ($multiPage) { | |
1011 $messages = array_merge($messages, $this->rc->storage->list_messages('', $startPage+1)); | |
1012 } | |
1013 //FIRST: 0 QUANTITY: 2 | |
1014 $sliceTo = $quantity + 1; | |
1015 $mslice = array_slice($messages, $firstArrayElement, $sliceTo, true); | |
1016 $messages = $mslice; | |
1017 $avbox = array(); | |
1018 $showAvmbox = false; | |
1019 foreach ($messages as $set_flag) { | |
1020 $set_flag->flags['skip_mbox_check'] = true; | |
1021 if ($showMboxColumn === true) { | |
1022 $set_flag->avmbox = $mailbox; | |
1023 $avbox[] = 'avmbox'; | |
1024 $showAvmbox = true; | |
1025 } | |
1026 } | |
1027 $uid_mboxes = $this->rcmail_js_message_list($messages, false, null, $showAvmbox, $avbox, $showMboxColumn); | |
1028 | |
1029 return $uid_mboxes; | |
1030 } | |
1031 | |
1032 public function save_search() | |
1033 { | |
1034 $search_name = rcube_utils::get_input_value('search_name', rcube_utils::INPUT_GPC); | |
1035 if ($search_name) { | |
1036 $search = array(); | |
1037 $search['search'] = rcube_utils::get_input_value('search', rcube_utils::INPUT_GPC); | |
1038 $search['search_name'] = $search_name; | |
1039 $search['folder'] = rcube_utils::get_input_value('folder', rcube_utils::INPUT_GPC); | |
1040 $search['sub_folders'] = rcube_utils::get_input_value('sub_folders', rcube_utils::INPUT_GPC); | |
1041 $prefs = (array)$this->rc->user->get_prefs(); | |
1042 if (!isset($prefs['advanced_search'])) { | |
1043 $prefs['advanced_search'] = array(); | |
1044 } | |
1045 $prefs['advanced_search'][$search_name] = $search; | |
1046 $this->rc->user->save_prefs(array('advanced_search' => $prefs['advanced_search'])); | |
1047 $this->rc->output->show_message('"<i>' . $search_name . '</i>" ' . $this->i18n_strings['has_been_saved'], 'confirmation'); | |
1048 } | |
1049 } | |
1050 | |
1051 public function delete_search() | |
1052 { | |
1053 $search_name = rcube_utils::get_input_value('search_name', rcube_utils::INPUT_GPC); | |
1054 if ($search_name) { | |
1055 $prefs = (array)$this->rc->user->get_prefs(); | |
1056 unset($prefs['advanced_search'][$search_name]); | |
1057 $this->rc->user->save_prefs(array('advanced_search' => $prefs['advanced_search'])); | |
1058 $this->rc->output->show_message('"<i>' . $search_name . '</i>" ' . $this->i18n_strings['has_been_deleted'], 'notice'); | |
1059 } | |
1060 } | |
1061 | |
1062 public function get_saved_search() | |
1063 { | |
1064 $search_name = rcube_utils::get_input_value('search_name', rcube_utils::INPUT_GPC); | |
1065 $prefs = (array)$this->rc->user->get_prefs(); | |
1066 if (!isset($prefs['advanced_search'])) { | |
1067 $prefs['advanced_search'] = array(); | |
1068 } | |
1069 | |
1070 $search = isset($prefs['advanced_search'][$search_name]) ? $prefs['advanced_search'][$search_name] : false; | |
1071 $this->rc->output->command('plugin.load_saved_search', $search); | |
1072 $this->rc->output->send(); | |
1073 } | |
1074 | |
1075 private function get_saved_search_names() | |
1076 { | |
1077 $prefs = (array)$this->rc->user->get_prefs(); | |
1078 if (!isset($prefs['advanced_search'])) { | |
1079 $prefs['advanced_search'] = array(); | |
1080 } | |
1081 $names = array(); | |
1082 foreach($prefs['advanced_search'] as $name => $search) { | |
1083 $names[] = $name; | |
1084 } | |
1085 | |
1086 return $names; | |
1087 } | |
1088 } |