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