1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16: class IMP_Search implements ArrayAccess, Iterator, Serializable
17: {
18:
19: const MBOX_PREFIX = "impsearch\0";
20:
21:
22: const BASIC_SEARCH = 'impbsearch';
23: const DIMP_FILTERSEARCH = 'dimpfsearch';
24: const DIMP_QUICKSEARCH = 'dimpqsearch';
25:
26:
27: const LIST_FILTER = 1;
28: const LIST_QUERY = 2;
29: const LIST_VFOLDER = 4;
30: const LIST_DISABLED = 8;
31:
32:
33: const CREATE_FILTER = 1;
34: const CREATE_QUERY = 2;
35: const CREATE_VFOLDER = 3;
36:
37: 38: 39: 40: 41:
42: public $changed = false;
43:
44: 45: 46: 47: 48:
49: protected $_filter = 0;
50:
51: 52: 53: 54: 55: 56: 57: 58: 59:
60: protected $_search = array(
61: 'filters' => array(),
62: 'query' => array(),
63: 'vfolders' => array()
64: );
65:
66: 67: 68:
69: public function __construct()
70: {
71: $this->init();
72: }
73:
74: 75: 76:
77: public function init()
78: {
79: $this->_getFilters();
80: $this->_getVFolders();
81: }
82:
83: 84: 85: 86: 87: 88: 89: 90: 91: 92:
93: public function runSearch($ob, $id)
94: {
95: $id = $this->_strip($id);
96: $mbox = '';
97: $sorted = new IMP_Indices();
98:
99: if (!isset($this[$id]) || !($query_list = $this[$id]->query)) {
100: return $sorted;
101: }
102:
103:
104: $sortpref = IMP_Mailbox::get($this[$id])->getSort(true);
105: if ($sortpref['by'] == Horde_Imap_Client::SORT_THREAD) {
106: $sortpref['by'] = $GLOBALS['prefs']->getValue('sortdate');
107: }
108:
109: $imp_imap = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create();
110:
111: foreach ($query_list as $mbox => $query) {
112: if (!empty($ob)) {
113: $query->andSearch(array($ob));
114: }
115: $results = $imp_imap->search($mbox, $query, array(
116: 'sort' => array($sortpref['by'])
117: ));
118: if ($sortpref['dir']) {
119: $results['match']->reverse();
120: }
121: $sorted->add($mbox, $results['match']);
122: }
123:
124: return $sorted;
125: }
126:
127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143:
144: public function createQuery($criteria, array $opts = array())
145: {
146: $opts = array_merge(array(
147: 'id' => null,
148: 'label' => null,
149: 'mboxes' => array(),
150: 'subfolders' => array(),
151: 'type' => self::CREATE_QUERY
152: ), $opts);
153:
154:
155: $opts['mboxes'] = array_map('strval', $opts['mboxes']);
156: $opts['subfolders'] = array_map('strval', $opts['subfolders']);
157:
158: switch ($opts['type']) {
159: case self::CREATE_FILTER:
160: $cname = 'IMP_Search_Filter';
161: break;
162:
163: case self::CREATE_QUERY:
164: $cname = 'IMP_Search_Query';
165: if (empty($opts['mboxes']) && empty($opts['subfolders'])) {
166: throw new InvalidArgumentException('Search query requires at least one mailbox.');
167: }
168: break;
169:
170: case self::CREATE_VFOLDER:
171: $cname = 'IMP_Search_Vfolder';
172: if (empty($opts['mboxes']) && empty($opts['subfolders'])) {
173: throw new InvalidArgumentException('Search query requires at least one mailbox.');
174: }
175: break;
176: }
177:
178: $ob = new $cname(array_filter(array(
179: 'add' => $criteria,
180: 'all' => in_array(IMP_Search_Query::ALLSEARCH, $opts['mboxes']),
181: 'id' => $this->_strip($opts['id']),
182: 'label' => $opts['label'],
183: 'mboxes' => $opts['mboxes'],
184: 'subfolders' => $opts['subfolders']
185: )));
186:
187: switch ($opts['type']) {
188: case self::CREATE_FILTER:
189:
190: $this->_search['filters'][$ob->id] = $ob;
191: $this->setFilters($this->_search['filters']);
192: break;
193:
194: case self::CREATE_QUERY:
195: $this->_search['query'][$ob->id] = $ob;
196: break;
197:
198: case self::CREATE_VFOLDER:
199:
200: $this->_search['vfolders'][$ob->id] = $ob;
201: $this->setVFolders($this->_search['vfolders']);
202: break;
203: }
204:
205: $this->changed = true;
206:
207: return $ob;
208: }
209:
210: 211: 212: 213: 214:
215: public function setFilters($filters)
216: {
217: $GLOBALS['prefs']->setValue('filter', serialize(array_values($filters)));
218: $this->_getFilters();
219: }
220:
221: 222: 223:
224: protected function _getFilters()
225: {
226: $filters = array();
227:
228:
229: $di = new DirectoryIterator(IMP_BASE . '/lib/Search/Filter');
230: foreach ($di as $val) {
231: if ($val->isFile()) {
232: $cname = 'IMP_Search_Filter_' . $val->getBasename('.php');
233: if (($cname != 'IMP_Search_Filter_Builtin') &&
234: class_exists($cname)) {
235: $filter = new $cname();
236: $filters[$filter->id] = $filter;
237: }
238: }
239: }
240:
241: if ($f_list = $GLOBALS['prefs']->getValue('filter')) {
242: $f_list = @unserialize($f_list);
243: if (is_array($f_list)) {
244: foreach ($f_list as $val) {
245: if ($val instanceof IMP_Search_Filter) {
246: $filters[$val->id] = $val;
247: }
248: }
249: }
250: }
251:
252: $this->_search['filters'] = $filters;
253: $this->changed = true;
254: }
255:
256:
257: 258: 259: 260: 261: 262: 263:
264: public function isFilter($id, $editable = false)
265: {
266: return (isset($this->_search['filters'][$this->_strip($id)]) &&
267: (!$editable || $this[$id]->canEdit));
268: }
269:
270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280:
281: public function applyFilter($id, array $mboxes, $mid = null)
282: {
283: if (!$this->isFilter($id)) {
284: throw new InvalidArgumentException('Invalid filter ID given.');
285: }
286:
287: if (!is_null($mid)) {
288: $mid = $this->_strip($mid);
289: }
290:
291: $q_ob = $this[$id]->toQuery($mboxes, $mid);
292: $this->_search['query'][$q_ob->id] = $q_ob;
293: $this->changed = true;
294:
295: return $q_ob;
296: }
297:
298: 299: 300: 301: 302:
303: public function setVFolders($vfolders)
304: {
305: $GLOBALS['prefs']->setValue('vfolder', serialize(array_values($vfolders)));
306: $this->_getVFolders();
307: }
308:
309: 310: 311:
312: protected function _getVFolders()
313: {
314: $vf = array();
315:
316:
317: $di = new DirectoryIterator(IMP_BASE . '/lib/Search/Vfolder');
318: $disable = array('IMP_Search_Vfolder_Vtrash');
319:
320: foreach ($di as $val) {
321: if ($val->isFile()) {
322: $cname = 'IMP_Search_Vfolder_' . $val->getBasename('.php');
323: if (($cname != 'IMP_Search_Vfolder_Builtin') &&
324: class_exists($cname)) {
325: $vfolder = new $cname(array(
326: 'disable' => in_array($cname, $disable)
327: ));
328: $vf[$vfolder->id] = $vfolder;
329: }
330: }
331: }
332:
333: if ($pref_vf = $GLOBALS['prefs']->getValue('vfolder')) {
334: $pref_vf = @unserialize($pref_vf);
335: if (is_array($pref_vf)) {
336: foreach ($pref_vf as $val) {
337: if ($val instanceof IMP_Search_Vfolder) {
338: $vf[$val->id] = $val;
339: }
340: }
341: }
342: }
343:
344: 345:
346: if (IMP_Factory_Imaptree::initialized()) {
347: $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->updateVFolders($vf);
348: }
349:
350: $this->_search['vfolders'] = $vf;
351: $this->changed = true;
352: }
353:
354: 355: 356: 357: 358: 359: 360: 361: 362:
363: public function isVFolder($id, $editable = false)
364: {
365: return (isset($this->_search['vfolders'][$this->_strip($id)]) &&
366: (!$editable || $this[$id]->canEdit));
367: }
368:
369: 370: 371: 372: 373: 374: 375:
376: public function isVTrash($id)
377: {
378: return (($this->isVFolder($id)) &&
379: ($this[$id] instanceof IMP_Search_Vfolder_Vtrash));
380: }
381:
382: 383: 384: 385: 386: 387: 388:
389: public function isVinbox($id)
390: {
391: return (($this->isVFolder($id)) &&
392: ($this[$id] instanceof IMP_Search_Vfolder_Vinbox));
393: }
394:
395: 396: 397: 398: 399: 400: 401:
402: public function isQuery($id, $editable = false)
403: {
404: return (isset($this->_search['query'][$this->_strip($id)]) &&
405: (!$editable ||
406: !in_array($this[$id]->id, array(self::BASIC_SEARCH, self::DIMP_FILTERSEARCH, self::DIMP_QUICKSEARCH))));
407: }
408:
409: 410: 411: 412: 413: 414: 415:
416: public function editUrl($id)
417: {
418: return IMP_Mailbox::get($this->createSearchId($id))->url('search.php')->add(array('edit_query' => 1));
419: }
420:
421: 422: 423: 424: 425: 426: 427:
428: public function isSearchMbox($id)
429: {
430: return (strpos($id, self::MBOX_PREFIX) === 0);
431: }
432:
433: 434: 435: 436: 437: 438: 439: 440:
441: protected function _strip($id)
442: {
443: return $this->isSearchMbox($id)
444: ? substr($id, strlen(self::MBOX_PREFIX))
445: : strval($id);
446: }
447:
448: 449: 450: 451: 452: 453: 454:
455: public function createSearchId($id)
456: {
457: return self::MBOX_PREFIX . $this->_strip($id);
458: }
459:
460:
461:
462: public function offsetExists($offset)
463: {
464: $id = $this->_strip($offset);
465:
466: foreach (array_keys($this->_search) as $key) {
467: if (isset($this->_search[$key][$id])) {
468: return true;
469: }
470: }
471:
472: return false;
473: }
474:
475: public function offsetGet($offset)
476: {
477: $id = $this->_strip($offset);
478:
479: foreach (array_keys($this->_search) as $key) {
480: if (isset($this->_search[$key][$id])) {
481: return $this->_search[$key][$id];
482: }
483: }
484:
485: return null;
486: }
487:
488: 489: 490: 491: 492: 493: 494: 495:
496: public function offsetSet($offset, $value)
497: {
498: if (!($value instanceof IMP_Search_Query)) {
499: throw new InvalidArgumentException('$value must be a query object.');
500: }
501:
502: $id = $this->_strip($offset);
503:
504: foreach (array_keys($this->_search) as $key) {
505: if (isset($this->_search[$key][$id])) {
506: $this->_search[$key][$id] = $value;
507: $this->changed = true;
508:
509: if ($key == 'vfolders') {
510: $this->setVFolders($this->_search['vfolders']);
511: }
512: return;
513: }
514: }
515:
516: throw new InvalidArgumentException('Creating search queries by array index is not supported. Use createQuery() instead.');
517: }
518:
519: 520: 521: 522: 523:
524: public function offsetUnset($offset)
525: {
526: $id = $this->_strip($offset);
527:
528: foreach (array_keys($this->_search) as $val) {
529: if (isset($this->_search[$val][$id])) {
530: unset($this->_search[$val][$id]);
531: $this->changed = true;
532:
533: if ($val == 'vfolders') {
534: $this->setVFolders($this->_search['vfolders']);
535: }
536: break;
537: }
538: }
539: }
540:
541:
542:
543: public function current()
544: {
545: return (($key = key($this->_search)) !== null)
546: ? current($this->_search[$key])
547: : null;
548: }
549:
550: public function key()
551: {
552: return (($key = key($this->_search)) !== null)
553: ? key($this->_search[$key])
554: : null;
555: }
556:
557: public function next()
558: {
559: $curr = null;
560:
561: while (($skey = key($this->_search)) !== null) {
562: 563:
564: $curr = is_null($curr)
565: ? next($this->_search[$skey])
566: : current($this->_search[$skey]);
567:
568: while ($curr !== false) {
569: if ($this->_currValid()) {
570: return;
571: }
572: $curr = next($this->_search[$skey]);
573: }
574:
575: next($this->_search);
576: }
577: }
578:
579: public function rewind()
580: {
581: foreach (array_keys($this->_search) as $key) {
582: reset($this->_search[$key]);
583: }
584: reset($this->_search);
585:
586: if ($this->valid() && !$this->_currValid()) {
587: $this->next();
588: }
589: }
590:
591: public function valid()
592: {
593: return (key($this->_search) !== null);
594: }
595:
596:
597:
598: 599: 600: 601: 602: 603: 604: 605: 606: 607:
608: public function setIteratorFilter($mask = 0)
609: {
610: $this->_filter = $mask;
611: reset($this);
612: }
613:
614: 615: 616: 617: 618:
619: protected function _currValid()
620: {
621: if (!($ob = $this->current())) {
622: return false;
623: }
624:
625: if ($ob->enabled ||
626: ($this->_filter & self::LIST_DISABLED)) {
627: if (!$this->_filter) {
628: return true;
629: }
630:
631: if (($this->_filter & self::LIST_FILTER) &&
632: $this->isFilter($ob)) {
633: return true;
634: }
635:
636: if (($this->_filter & self::LIST_QUERY) &&
637: $this->isQuery($ob)) {
638: return true;
639: }
640:
641: if (($this->_filter & self::LIST_VFOLDER) &&
642: $this->isVFolder($ob)) {
643: return true;
644: }
645: }
646:
647: return false;
648: }
649:
650:
651:
652: 653: 654: 655: 656:
657: public function serialize()
658: {
659: return serialize($this->_search);
660: }
661:
662: 663: 664: 665: 666: 667: 668:
669: public function unserialize($data)
670: {
671: $data = @unserialize($data);
672: if (!is_array($data)) {
673: throw new Exception('Cache version change');
674: }
675:
676: $this->_search = $data;
677: }
678:
679: }
680: