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 }