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