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