1<?php
2/**
3 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (LGPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
7 *
8 * @category   Horde
9 * @copyright  2011-2016 Horde LLC
10 * @license    http://www.horde.org/licenses/lgpl21 LGPL 2.1
11 * @package    Imap_Client
12 * @subpackage UnitTests
13 */
14
15/**
16 * Tests for the Search Query object.
17 *
18 * @author     Michael Slusarz <slusarz@horde.org>
19 * @category   Horde
20 * @copyright  2011-2016 Horde LLC
21 * @ignore
22 * @license    http://www.horde.org/licenses/lgpl21 LGPL 2.1
23 * @package    Imap_Client
24 * @subpackage UnitTests
25 */
26class Horde_Imap_Client_SearchTest extends PHPUnit_Framework_TestCase
27{
28    /**
29     * @dataProvider flagQueryProvider
30     */
31    public function testFlagQuery($flags, $fuzzy, $expected)
32    {
33        $ob = new Horde_Imap_Client_Search_Query();
34
35        foreach ($flags as $val) {
36            $ob->flag($val[0], $val[1], array('fuzzy' => $fuzzy));
37        }
38
39        $this->assertTrue($ob->flagSearch());
40        $this->assertEquals(
41            $expected,
42            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
43        );
44    }
45
46    public function flagQueryProvider()
47    {
48        return array(
49            array(
50                array(
51                    /* System flag - set. */
52                    array('\\answered', true),
53                    /* System flag - not set. */
54                    array('\\draft', false),
55                    /* System flag - set. */
56                    array('foo', true),
57                    /* System flag - not set. */
58                    array('bar', false)
59                ),
60                false,
61                'ANSWERED UNDRAFT KEYWORD FOO UNKEYWORD BAR'
62            ),
63            array(
64                array(
65                    array('foo', true)
66                ),
67                true,
68                'FUZZY KEYWORD FOO'
69            )
70        );
71    }
72
73    /**
74     * @dataProvider newMsgsQueryProvider
75     */
76    public function testNewMsgsQuery($newmsgs, $fuzzy, $expected)
77    {
78        $ob = new Horde_Imap_Client_Search_Query();
79        $ob->newMsgs($newmsgs, array('fuzzy' => $fuzzy));
80
81        $this->assertEquals(
82            $expected,
83            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
84        );
85    }
86
87    public function newMsgsQueryProvider()
88    {
89        return array(
90            array(true, false, 'NEW'),
91            array(false, false, 'OLD'),
92            array(true, true, 'FUZZY NEW'),
93            array(false, true, 'FUZZY OLD')
94        );
95    }
96
97    /**
98     * @dataProvider headerTextQueryProvider
99     */
100    public function testHeaderTextQuery($not, $fuzzy, $expected)
101    {
102        $ob = new Horde_Imap_Client_Search_Query();
103        $ob->headerText('Foo', 'Bar', $not, array('fuzzy' => $fuzzy));
104
105        $this->assertEquals(
106            $expected,
107            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
108        );
109    }
110
111    public function headerTextQueryProvider()
112    {
113        return array(
114            array(false, false, 'HEADER FOO Bar'),
115            array(true, false, 'NOT HEADER FOO Bar'),
116            array(false, true, 'FUZZY HEADER FOO Bar'),
117            array(true, true, 'FUZZY NOT HEADER FOO Bar')
118        );
119    }
120
121    public function testHeaderTextUtf8Query()
122    {
123        $ob = new Horde_Imap_Client_Search_Query();
124        $ob->headerText('Foo', 'EëE');
125
126        try {
127            $ob->build();
128            $this->fail();
129        } catch (Horde_Imap_Client_Data_Format_Exception $e) {
130            // Expected
131        }
132
133        $ob->charset('UTF-8', false);
134
135        $this->assertNotEmpty($ob->build());
136    }
137
138    /**
139     * @dataProvider textQueryProvider
140     */
141    public function testTextQuery($body, $not, $fuzzy, $expected)
142    {
143        $ob = new Horde_Imap_Client_Search_Query();
144        $ob->text('foo', $body, $not, array('fuzzy' => $fuzzy));
145
146        $this->assertEquals(
147            $expected,
148            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
149        );
150    }
151
152    public function textQueryProvider()
153    {
154        return array(
155            array(true, false, false, 'BODY foo'),
156            array(false, false, false, 'TEXT foo'),
157            array(true, true, false, 'NOT BODY foo'),
158            array(false, true, false, 'NOT TEXT foo'),
159            array(true, false, true, 'FUZZY BODY foo'),
160            array(false, false, true, 'FUZZY TEXT foo'),
161            array(true, true, true, 'FUZZY NOT BODY foo'),
162            array(false, true, true, 'FUZZY NOT TEXT foo')
163        );
164    }
165
166    /**
167     * @dataProvider sizeQueryProvider
168     */
169    public function testSizeQuery($larger, $not, $fuzzy, $expected)
170    {
171        $ob = new Horde_Imap_Client_Search_Query();
172        $ob->size(100, $larger, $not, array('fuzzy' => $fuzzy));
173
174        $this->assertEquals(
175            $expected,
176            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
177        );
178    }
179
180    public function sizeQueryProvider()
181    {
182        return array(
183            array(true, false, false, 'LARGER 100'),
184            array(false, false, false, 'SMALLER 100'),
185            array(true, true, false, 'NOT LARGER 100'),
186            array(false, true, false, 'NOT SMALLER 100'),
187            array(true, false, true, 'FUZZY LARGER 100'),
188            array(false, false, true, 'FUZZY SMALLER 100'),
189            array(true, true, true, 'FUZZY NOT LARGER 100'),
190            array(false, true, true, 'FUZZY NOT SMALLER 100')
191        );
192    }
193
194    /**
195     * @dataProvider idsQueryProvider
196     */
197    public function testIdsQuery($ids, $not, $fuzzy, $expected)
198    {
199        $ob = new Horde_Imap_Client_Search_Query();
200        $ob->ids(new Horde_Imap_Client_Ids($ids), $not, array(
201            'fuzzy' => $fuzzy
202        ));
203
204        $this->assertEquals(
205            $expected,
206            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
207        );
208    }
209
210    public function idsQueryProvider()
211    {
212        return array(
213            array('1,2,3', false, false, 'UID 1:3'),
214            array('1:3', true, false, 'NOT UID 1:3'),
215            array('1,2,3', false, true, 'FUZZY UID 1:3'),
216            array('1:3', true, true, 'FUZZY NOT UID 1:3')
217        );
218    }
219
220    /**
221     * @dataProvider dateSearchQueryProvider
222     */
223    public function testDateSearchQuery(
224        $range, $header, $not, $fuzzy, $expected
225    )
226    {
227        $ob = new Horde_Imap_Client_Search_Query();
228        $ob->dateSearch(
229            new DateTime('January 1, 2010'),
230            $range,
231            $header,
232            $not,
233            array('fuzzy' => $fuzzy)
234        );
235
236        $this->assertEquals(
237            $expected,
238            $fuzzy ? $this->_fuzzy($ob) : strval($ob)
239        );
240    }
241
242    public function dateSearchQueryProvider()
243    {
244        return array(
245            array(
246                Horde_Imap_Client_Search_Query::DATE_BEFORE,
247                true,
248                false,
249                false,
250                'SENTBEFORE 1-Jan-2010',
251            ),
252            array(
253                Horde_Imap_Client_Search_Query::DATE_BEFORE,
254                false,
255                false,
256                false,
257                'BEFORE 1-Jan-2010',
258            ),
259            array(
260                Horde_Imap_Client_Search_Query::DATE_BEFORE,
261                true,
262                true,
263                false,
264                'NOT SENTBEFORE 1-Jan-2010',
265            ),
266            array(
267                Horde_Imap_Client_Search_Query::DATE_BEFORE,
268                false,
269                true,
270                false,
271                'NOT BEFORE 1-Jan-2010',
272            ),
273            array(
274                Horde_Imap_Client_Search_Query::DATE_BEFORE,
275                true,
276                true,
277                true,
278                'FUZZY NOT SENTBEFORE 1-Jan-2010',
279            ),
280            array(
281                Horde_Imap_Client_Search_Query::DATE_BEFORE,
282                false,
283                true,
284                true,
285                'FUZZY NOT BEFORE 1-Jan-2010',
286            ),
287            array(
288                Horde_Imap_Client_Search_Query::DATE_ON,
289                true,
290                false,
291                false,
292                'SENTON 1-Jan-2010',
293            ),
294            array(
295                Horde_Imap_Client_Search_Query::DATE_ON,
296                false,
297                false,
298                false,
299                'ON 1-Jan-2010',
300            ),
301            array(
302                Horde_Imap_Client_Search_Query::DATE_ON,
303                true,
304                true,
305                false,
306                'NOT SENTON 1-Jan-2010',
307            ),
308            array(
309                Horde_Imap_Client_Search_Query::DATE_ON,
310                false,
311                true,
312                false,
313                'NOT ON 1-Jan-2010',
314            ),
315            array(
316                Horde_Imap_Client_Search_Query::DATE_ON,
317                true,
318                true,
319                true,
320                'FUZZY NOT SENTON 1-Jan-2010',
321            ),
322            array(
323                Horde_Imap_Client_Search_Query::DATE_ON,
324                false,
325                true,
326                true,
327                'FUZZY NOT ON 1-Jan-2010',
328            ),
329            array(
330                Horde_Imap_Client_Search_Query::DATE_SINCE,
331                true,
332                false,
333                false,
334                'SENTSINCE 1-Jan-2010',
335            ),
336            array(
337                Horde_Imap_Client_Search_Query::DATE_SINCE,
338                false,
339                false,
340                false,
341                'SINCE 1-Jan-2010',
342            ),
343            array(
344                Horde_Imap_Client_Search_Query::DATE_SINCE,
345                true,
346                true,
347                false,
348                'NOT SENTSINCE 1-Jan-2010',
349            ),
350            array(
351                Horde_Imap_Client_Search_Query::DATE_SINCE,
352                false,
353                true,
354                false,
355                'NOT SINCE 1-Jan-2010',
356            ),
357            array(
358                Horde_Imap_Client_Search_Query::DATE_SINCE,
359                true,
360                true,
361                true,
362                'FUZZY NOT SENTSINCE 1-Jan-2010',
363            ),
364            array(
365                Horde_Imap_Client_Search_Query::DATE_SINCE,
366                false,
367                true,
368                true,
369                'FUZZY NOT SINCE 1-Jan-2010',
370            )
371        );
372    }
373
374    /**
375     * @dataProvider intervalSearchQueryProvider
376     */
377    public function testIntervalSearchQuery($range, $not, $fuzzy, $expected)
378    {
379        $ob = new Horde_Imap_Client_Search_Query();
380        $ob->intervalSearch(30, $range, $not, array('fuzzy' => $fuzzy));
381
382        $this->assertEquals(
383            $expected,
384            $fuzzy ? $this->_fuzzy($ob, array('WITHIN')) : strval($ob)
385        );
386    }
387
388    public function intervalSearchQueryProvider()
389    {
390        return array(
391            array(
392                Horde_Imap_Client_Search_Query::INTERVAL_OLDER,
393                false,
394                false,
395                'OLDER 30'
396            ),
397            array(
398                Horde_Imap_Client_Search_Query::INTERVAL_OLDER,
399                true,
400                false,
401                'NOT OLDER 30'
402            ),
403            array(
404                Horde_Imap_Client_Search_Query::INTERVAL_OLDER,
405                false,
406                true,
407                'FUZZY OLDER 30'
408            ),
409            array(
410                Horde_Imap_Client_Search_Query::INTERVAL_OLDER,
411                true,
412                true,
413                'FUZZY NOT OLDER 30'
414            ),
415            array(
416                Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER,
417                false,
418                false,
419                'YOUNGER 30'
420            ),
421            array(
422                Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER,
423                true,
424                false,
425                'NOT YOUNGER 30'
426            ),
427            array(
428                Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER,
429                false,
430                true,
431                'FUZZY YOUNGER 30'
432            ),
433            array(
434                Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER,
435                true,
436                true,
437                'FUZZY NOT YOUNGER 30'
438            )
439        );
440    }
441
442    public function testOrQueries()
443    {
444        $ob2 = new Horde_Imap_Client_Search_Query();
445        $ob2->flag('\\deleted', false);
446        $ob2->headerText('from', 'ABC');
447
448        $ob3 = new Horde_Imap_Client_Search_Query();
449        $ob3->flag('\\deleted', true);
450        $ob3->headerText('from', 'DEF');
451
452        $ob = new Horde_Imap_Client_Search_Query();
453        $ob->orSearch(array($ob2, $ob3));
454
455        $this->assertEquals(
456            'OR (DELETED FROM DEF) (UNDELETED FROM ABC)',
457            strval($ob)
458        );
459
460        $ob4 = new Horde_Imap_Client_Search_Query();
461        $ob4->flag('\\flagged', true);
462        $ob4->headerText('from', 'GHI');
463
464        $ob5 = new Horde_Imap_Client_Search_Query();
465        $ob5->orSearch(array($ob2, $ob3, $ob4));
466
467        $this->assertEquals(
468            'OR (FLAGGED FROM GHI) (OR (DELETED FROM DEF) (UNDELETED FROM ABC))',
469            strval($ob5)
470        );
471    }
472
473    public function testOrQueriesWithABaseQuery()
474    {
475        $or_ob = new Horde_Imap_Client_Search_Query();
476
477        $ob = new Horde_Imap_Client_Search_Query();
478        $ob->flag('\\deleted', false);
479        $ob->headerText('from', 'ABC');
480        $or_ob->orSearch($ob);
481
482        $ob = new Horde_Imap_Client_Search_Query();
483        $ob->flag('\\deleted', true);
484        $ob->headerText('from', 'DEF');
485        $or_ob->orSearch($ob);
486
487        $base_ob = new Horde_Imap_Client_Search_Query();
488        $base_ob->flag('\\seen', false);
489        $base_ob->andSearch($or_ob);
490
491        $this->assertEquals(
492            'UNSEEN OR (DELETED FROM DEF) (UNDELETED FROM ABC)',
493             strval($base_ob)
494         );
495    }
496
497    /**
498     * @dataProvider modseqSearchQueryProvider
499     */
500    public function testModseq($name, $type, $not, $fuzzy, $expected)
501    {
502        $ob = new Horde_Imap_Client_Search_Query();
503        $ob->modseq(123, $name, $type, $not, array('fuzzy' => $fuzzy));
504
505        $this->assertEquals(
506            $expected,
507            $fuzzy ? $this->_fuzzy($ob, array('CONDSTORE')) : strval($ob)
508        );
509    }
510
511    public function modseqSearchQueryProvider()
512    {
513        return array(
514            array(null, null, false, false, 'MODSEQ 123'),
515            array(null, null, true, false, 'NOT MODSEQ 123'),
516            array(null, null, false, true, 'FUZZY MODSEQ 123'),
517            array(null, null, true, true, 'FUZZY NOT MODSEQ 123'),
518            array('foo', null, false, false, 'MODSEQ "foo" all 123'),
519            array('foo', null, true, false, 'NOT MODSEQ "foo" all 123'),
520            array('foo', null, false, true, 'FUZZY MODSEQ "foo" all 123'),
521            array('foo', null, true, true, 'FUZZY NOT MODSEQ "foo" all 123'),
522            array('foo', 'all', false, false, 'MODSEQ "foo" all 123'),
523            array('foo', 'all', true, false, 'NOT MODSEQ "foo" all 123'),
524            array('foo', 'all', false, true, 'FUZZY MODSEQ "foo" all 123'),
525            array('foo', 'all', true, true, 'FUZZY NOT MODSEQ "foo" all 123'),
526            array('foo', 'shared', false, false, 'MODSEQ "foo" shared 123'),
527            array('foo', 'shared', true, false, 'NOT MODSEQ "foo" shared 123'),
528            array('foo', 'shared', false, true, 'FUZZY MODSEQ "foo" shared 123'),
529            array('foo', 'shared', true, true, 'FUZZY NOT MODSEQ "foo" shared 123'),
530            array('foo', 'priv', false, false, 'MODSEQ "foo" priv 123'),
531            array('foo', 'priv', true, false, 'NOT MODSEQ "foo" priv 123'),
532            array('foo', 'priv', false, true, 'FUZZY MODSEQ "foo" priv 123'),
533            array('foo', 'priv', true, true, 'FUZZY NOT MODSEQ "foo" priv 123')
534        );
535    }
536
537    /**
538     * @dataProvider previousSearchQueryProvider
539     */
540    public function testPreviousSearchQuery($not, $fuzzy, $expected)
541    {
542        $ob = new Horde_Imap_Client_Search_Query();
543        $ob->previousSearch($not, array('fuzzy' => $fuzzy));
544
545        $this->assertEquals(
546            $expected,
547            $fuzzy ? $this->_fuzzy($ob, array('ESEARCH', 'SEARCHRES')) : strval($ob)
548        );
549    }
550
551    public function previousSearchQueryProvider()
552    {
553        return array(
554            array(false, false, 'NOT $'),
555            array(true, false, '$'),
556            array(false, true, 'FUZZY NOT $'),
557            array(true, true, 'FUZZY $')
558        );
559    }
560
561    public function testClone()
562    {
563        $ob = new Horde_Imap_Client_Search_Query();
564        $ob->text('foo');
565
566        $ob2 = clone $ob;
567        $ob2->text('bar');
568
569        $this->assertEquals(
570            'BODY foo',
571            strval($ob)
572        );
573        $this->assertEquals(
574            'BODY foo BODY bar',
575            strval($ob2)
576        );
577    }
578
579    public function testSerialize()
580    {
581        $ob = new Horde_Imap_Client_Search_Query();
582        $ob->ids(new Horde_Imap_Client_Ids('1:3'), true);
583        $ob->text('foo');
584        $ob->charset('US-ASCII', false);
585
586        $this->assertEquals(
587            'NOT UID 1:3 BODY foo',
588            strval(unserialize(serialize($ob)))
589        );
590    }
591
592    public function testBug13971()
593    {
594        $ob = new Horde_Imap_Client_Search_Query();
595        $ob->ids(new Horde_Imap_Client_Ids(array()));
596        $ob->text('foo');
597
598        $this->assertEquals(
599            '',
600            strval($ob)
601        );
602
603        $ob2 = new Horde_Imap_Client_Search_Query();
604        $ob2->text('foo2');
605        $ob2->andSearch($ob);
606
607        $this->assertEquals(
608            '',
609            strval($ob2)
610        );
611
612        $ob3 = new Horde_Imap_Client_Search_Query();
613        $ob3->text('foo3');
614        $ob3->orSearch($ob);
615
616        $this->assertEquals(
617            'BODY foo3',
618            strval($ob3)
619        );
620
621        $ob2->orSearch($ob3);
622
623        $this->assertEquals(
624            'BODY foo3',
625            strval($ob2)
626        );
627
628        /* A NOT qualifier on an empty ID list should ignore the list. */
629        $ob->ids(new Horde_Imap_Client_Ids(array()), true);
630
631        $this->assertEquals(
632            'BODY foo',
633            strval($ob)
634        );
635    }
636
637    /**
638     * @dataProvider inconsistentCharsetsInAndOrSearchesProvider
639     * @expectedException InvalidArgumentException
640     */
641    public function testInconsistentCharsetsInAndOrSearches($query)
642    {
643        $query->build(null);
644    }
645
646    public function inconsistentCharsetsInAndOrSearchesProvider()
647    {
648        $ob = new Horde_Imap_Client_Search_Query();
649        $ob->text('foo');
650        $ob->charset('UTF-8', false);
651
652        $ob2 = new Horde_Imap_Client_Search_Query();
653        $ob2->text('foo2');
654        $ob2->charset('ISO-8859-1', false);
655        $ob2->andSearch($ob);
656
657        $ob3 = new Horde_Imap_Client_Search_Query();
658        $ob3->text('foo2');
659        $ob3->charset('ISO-8859-1', false);
660        $ob3->orSearch($ob);
661
662        return array(array($ob2), array($ob3));
663    }
664
665    private function _fuzzy($ob, array $exts = array())
666    {
667        $capability = new Horde_Imap_Client_Data_Capability_Imap();
668        $capability->add('SEARCH', 'FUZZY');
669        foreach ($exts as $val) {
670            $capability->add($val);
671        }
672
673        $res = $ob->build($capability);
674        return $res['query']->escape();
675    }
676
677}
678