1: <?php
2: /**
3: * Copyright 2010-2014 Horde LLC (http://www.horde.org/)
4: *
5: * See the enclosed file COPYING for license information (GPL). If you
6: * did not receive this file, see http://www.horde.org/licenses/gpl.
7: *
8: * @copyright 2010-2014 Horde LLC
9: * @category Horde
10: * @license http://www.horde.org/licenses/gpl GPL
11: * @package IMP
12: */
13:
14: /**
15: * Data structure for a search query.
16: *
17: * @author Michael Slusarz <slusarz@horde.org>
18: * @category Horde
19: * @copyright 2010-2014 Horde LLC
20: * @license http://www.horde.org/licenses/gpl GPL
21: * @package IMP
22: *
23: * @property boolean $all Does this query search all mailboxes?
24: * @property boolean $canEdit Can this query be edited?
25: * @property string $formid The mailbox ID to use in forms.
26: * @property string $id The query ID.
27: * @property string $label The query label.
28: * @property array $mboxes The list of mailboxes to query. This list
29: * automatically expands subfolder searches.
30: * @property array $mbox_list The list of individual mailboxes to query (no
31: * subfolder mailboxes).
32: * @property IMP_Mailbox $mbox_ob The IMP_Mailbox object for this query.
33: * @property string $mid The query ID with the search mailbox prefix.
34: * @property array $query The list of IMAP queries that comprise this search.
35: * Keys are mailbox names, values are
36: * Horde_Imap_Client_Search_Query objects.
37: * @property string $querytext The textual representation of the query.
38: * @property array $subfolder_list The list of mailboxes to do subfolder
39: * queries for. The subfolders are not
40: * expanded.
41: */
42: class IMP_Search_Query implements Serializable
43: {
44: /* Serialized version. */
45: const VERSION = 1;
46:
47: /* Prefix indicating subfolder search. */
48: const SUBFOLDER = "sub\0";
49:
50: /* Mailbox value indicating a search all mailboxes search. */
51: const ALLSEARCH = "all\0";
52:
53: /**
54: * Is this query enabled?
55: *
56: * @var boolean
57: */
58: public $enabled = true;
59:
60: /**
61: * Cache results.
62: *
63: * @var array
64: */
65: protected $_cache = array();
66:
67: /**
68: * Can this query be edited?
69: *
70: * @var boolean
71: */
72: protected $_canEdit = true;
73:
74: /**
75: * The search criteria (IMP_Search_Element objects).
76: *
77: * @var array
78: */
79: protected $_criteria = array();
80:
81: /**
82: * The search ID.
83: *
84: * @var string
85: */
86: protected $_id;
87:
88: /**
89: * The virtual folder label.
90: *
91: * @var string
92: */
93: protected $_label;
94:
95: /**
96: * The mailbox list.
97: *
98: * @var array
99: */
100: protected $_mboxes = array();
101:
102: /**
103: * List of serialize entries not to save.
104: *
105: * @var array
106: */
107: protected $_nosave = array();
108:
109: /**
110: * Constructor.
111: *
112: * @var array $opts Options:
113: * - add: (array) A list of criteria to add (Horde_Search_Element
114: * objects).
115: * DEFAULT: No criteria explicitly added.
116: * - all: (boolean) Search all mailboxes? If set, ignores 'mboxes' and
117: * 'subfolders' options.
118: * DEFAULT: false
119: * - disable: (boolean) Disable this query?
120: * DEFAULT: false
121: * - id: (string) Use this ID.
122: * DEFAULT: ID automatically generated.
123: * - label: (string) The label for this query.
124: * DEFAULT: Search Results
125: * - mboxes: (array) The list of mailboxes to search.
126: * DEFAULT: None
127: * - subfolders: (array) The list of mailboxes to do subfolder searches
128: * for.
129: * DEFAULT: None
130: */
131: public function __construct(array $opts = array())
132: {
133: $this->enabled = empty($opts['disable']);
134: if (isset($opts['add'])) {
135: $this->replace($opts['add']);
136: }
137:
138: $this->_id = isset($opts['id'])
139: ? $opts['id']
140: : strval(new Horde_Support_Randomid());
141:
142: $this->_label = isset($opts['label'])
143: ? $opts['label']
144: : _("Search Results");
145:
146: if (!empty($opts['all'])) {
147: $this->_mboxes = array(self::ALLSEARCH);
148: } else {
149: if (isset($opts['mboxes'])) {
150: $this->_mboxes = $opts['mboxes'];
151: }
152:
153: if (isset($opts['subfolders'])) {
154: foreach ($opts['subfolders'] as $val) {
155: $this->_mboxes[] = self::SUBFOLDER . $val;
156: }
157: }
158:
159: natsort($this->_mboxes);
160: }
161: }
162:
163: /**
164: */
165: public function __get($name)
166: {
167: global $injector;
168:
169: switch ($name) {
170: case 'all':
171: return in_array(self::ALLSEARCH, $this->_mboxes);
172:
173: case 'canEdit':
174: return $this->_canEdit;
175:
176: case 'criteria':
177: $out = array();
178: foreach ($this->_criteria as $elt) {
179: $out[] = array(
180: 'criteria' => $elt->getCriteria(),
181: 'element' => get_class($elt)
182: );
183: }
184: return $out;
185:
186: case 'formid':
187: return $this->mbox_ob->form_to;
188:
189: case 'id':
190: return $this->_id;
191:
192: case 'label':
193: return $this->_label;
194:
195: case 'mboxes':
196: if (!isset($this->_cache['mboxes'])) {
197: $out = $this->mbox_list;
198:
199: if (!$this->all &&
200: ($s_list = $this->subfolder_list)) {
201: foreach ($s_list as $val) {
202: $out = array_merge($out, $val->subfolders);
203: }
204: }
205:
206: $this->_cache['mboxes'] = array_unique($out, SORT_REGULAR);
207: }
208:
209: return $this->_cache['mboxes'];
210:
211: case 'mbox_list':
212: case 'subfolder_list':
213: if (!isset($this->_cache['mbox_list'])) {
214: $mbox = $subfolder = array();
215:
216: if ($this->all) {
217: $mbox = array();
218: $iterator = new IMP_Ftree_IteratorFilter(
219: $injector->getInstance('IMP_Ftree')
220: );
221: $iterator->add(array(
222: $iterator::CONTAINERS,
223: $iterator::NONIMAP
224: ));
225:
226: foreach ($iterator as $val) {
227: $mbox[] = $val->mbox_ob;
228: }
229: } else {
230: foreach ($this->_mboxes as $val) {
231: if (strpos($val, self::SUBFOLDER) === 0) {
232: $subfolder[] = IMP_Mailbox::get(substr($val, strlen(self::SUBFOLDER)));
233: } else {
234: $mbox[] = IMP_Mailbox::get($val);
235: }
236: }
237: }
238:
239: $this->_cache['mbox_list'] = $mbox;
240: $this->_cache['subfolder_list'] = $subfolder;
241: }
242:
243: return $this->_cache[$name];
244:
245: case 'mbox_ob':
246: return IMP_Mailbox::get($this->mid);
247:
248: case 'mid':
249: return IMP_Search::MBOX_PREFIX . $this->_id;
250:
251: case 'query':
252: $qout = array();
253:
254: foreach ($this->mboxes as $mbox) {
255: $query = new Horde_Imap_Client_Search_Query();
256: foreach ($this->_criteria as $elt) {
257: $query = $elt->createQuery($mbox, $query);
258: }
259: $qout[strval($mbox)] = $query;
260: }
261:
262: return $qout;
263:
264: case 'querytext':
265: $text = array();
266:
267: foreach ($this->_criteria as $elt) {
268: if ($elt instanceof IMP_Search_Element_Or) {
269: array_pop($text);
270: $text[] = $elt->queryText();
271: } else {
272: $text[] = $elt->queryText();
273: $text[] = _("and");
274: }
275: }
276: array_pop($text);
277:
278: $mbox_display = array();
279:
280: if ($this->all) {
281: $mbox_display[] = _("All Mailboxes");
282: } else {
283: foreach ($this->mboxes as $val) {
284: $mbox_display[] = $val->display;
285: }
286: }
287:
288: return sprintf(_("Search %s in %s"), implode(' ', $text), '[' . implode(', ', $mbox_display) . ']');
289: }
290: }
291:
292: /**
293: * String representation of this object: the mailbox ID.
294: *
295: * @return string Mailbox ID.
296: */
297: public function __toString()
298: {
299: return $this->mid;
300: }
301:
302: /**
303: * Add a search query element.
304: *
305: * @param IMP_Search_Element $elt The search element to add.
306: */
307: public function add(IMP_Search_Element $elt)
308: {
309: $this->_criteria[] = $elt;
310: }
311:
312: /**
313: * Replace the search query with the given query.
314: *
315: * @param array $criteria A list of criteria to add (Horde_Search_Element
316: * objects).
317: */
318: public function replace(array $criteria = array())
319: {
320: $this->_criteria = array();
321:
322: foreach ($criteria as $val) {
323: $this->add($val);
324: }
325: }
326:
327: /* Serializable methods. */
328:
329: /**
330: * Serialization.
331: *
332: * @return string Serialized data.
333: */
334: public function serialize()
335: {
336: $data = array_filter(array(
337: 'c' => $this->_criteria,
338: 'e' => intval($this->enabled),
339: 'i' => $this->_id,
340: 'l' => $this->_label,
341: 'm' => $this->_mboxes,
342: 'v' => self::VERSION
343: ));
344:
345: foreach ($this->_nosave as $val) {
346: unset($data[$val]);
347: }
348:
349: return serialize($data);
350: }
351:
352: /**
353: * Unserialization.
354: *
355: * @param string $data Serialized data.
356: *
357: * @throws Exception
358: */
359: public function unserialize($data)
360: {
361: $data = unserialize($data);
362: if (!is_array($data) ||
363: !isset($data['v']) ||
364: ($data['v'] != self::VERSION)) {
365: throw new Exception('Cache version change');
366: }
367:
368: if (isset($data['c'])) {
369: $this->_criteria = $data['c'];
370: }
371: $this->enabled = !empty($data['e']);
372: if (isset($data['i'])) {
373: $this->_id = $data['i'];
374: }
375: if (isset($data['l'])) {
376: $this->_label = $data['l'];
377: }
378: if (isset($data['m'])) {
379: $this->_mboxes = $data['m'];
380: }
381: }
382:
383: }
384: