Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
1 <?php 2 /** 3 * Copyright 2008-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file LICENSE 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 2008-2017 Horde LLC 10 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 11 * @package Imap_Client 12 */ 13 14 /** 15 * Abstraction of the IMAP4rev1 search criteria (see RFC 3501 [6.4.4]). 16 * Allows translation between abstracted search criteria and a generated IMAP 17 * search criteria string suitable for sending to a remote IMAP server. 18 * 19 * @author Michael Slusarz <slusarz@horde.org> 20 * @category Horde 21 * @copyright 2008-2017 Horde LLC 22 * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 23 * @package Imap_Client 24 */ 25 class Horde_Imap_Client_Search_Query implements Serializable 26 { 27 /** 28 * Serialized version. 29 */ 30 const VERSION = 3; 31 32 /** 33 * Constants for dateSearch() 34 */ 35 const DATE_BEFORE = 'BEFORE'; 36 const DATE_ON = 'ON'; 37 const DATE_SINCE = 'SINCE'; 38 39 /** 40 * Constants for intervalSearch() 41 */ 42 const INTERVAL_OLDER = 'OLDER'; 43 const INTERVAL_YOUNGER = 'YOUNGER'; 44 45 /** 46 * The charset of the search strings. All text strings must be in 47 * this charset. By default, this is 'US-ASCII' (see RFC 3501 [6.4.4]). 48 * 49 * @var string 50 */ 51 protected $_charset = null; 52 53 /** 54 * The list of search params. 55 * 56 * @var array 57 */ 58 protected $_search = array(); 59 60 /** 61 * String representation: The IMAP search string. 62 */ 63 public function __toString() 64 { 65 try { 66 $res = $this->build(null); 67 return $res['query']->escape(); 68 } catch (Exception $e) { 69 return ''; 70 } 71 } 72 73 /** 74 * Sets the charset of the search text. 75 * 76 * @param string $charset The charset to use for the search. 77 * @param boolean $convert Convert existing text values? 78 * 79 * @throws Horde_Imap_Client_Exception_SearchCharset 80 */ 81 public function charset($charset, $convert = true) 82 { 83 $oldcharset = $this->_charset; 84 $this->_charset = Horde_String::upper($charset); 85 86 if (!$convert || ($oldcharset == $this->_charset)) { 87 return; 88 } 89 90 foreach (array('and', 'or') as $item) { 91 if (isset($this->_search[$item])) { 92 foreach ($this->_search[$item] as &$val) { 93 $val->charset($charset, $convert); 94 } 95 } 96 } 97 98 // Unset the reference to avoid corrupting $this->_search below. 99 unset($val); 100 101 foreach (array('header', 'text') as $item) { 102 if (isset($this->_search[$item])) { 103 foreach ($this->_search[$item] as $key => $val) { 104 $new_val = Horde_String::convertCharset($val['text'], $oldcharset, $this->_charset); 105 if (Horde_String::convertCharset($new_val, $this->_charset, $oldcharset) != $val['text']) { 106 throw new Horde_Imap_Client_Exception_SearchCharset($this->_charset); 107 } 108 $this->_search[$item][$key]['text'] = $new_val; 109 } 110 } 111 } 112 } 113 114 /** 115 * Builds an IMAP4rev1 compliant search string. 116 * 117 * @todo Change default of $exts to null. 118 * 119 * @param Horde_Imap_Client_Base $exts The server object this query will 120 * be run on (@since 2.24.0), a 121 * Horde_Imap_Client_Data_Capability 122 * object (@since 2.24.0), or the 123 * list of extensions present 124 * on the server (@deprecated). 125 * If null, all extensions are 126 * assumed to be available. 127 * 128 * @return array An array with these elements: 129 * - charset: (string) The charset of the search string. If null, no 130 * text strings appear in query. 131 * - exts: (array) The list of IMAP extensions used to create the 132 * string. 133 * - query: (Horde_Imap_Client_Data_Format_List) The IMAP search 134 * command. 135 * 136 * @throws Horde_Imap_Client_Data_Format_Exception 137 * @throws Horde_Imap_Client_Exception_NoSupportExtension 138 */ 139 public function build($exts = array()) 140 { 141 /* @todo: BC */ 142 if (is_array($exts)) { 143 $tmp = new Horde_Imap_Client_Data_Capability_Imap(); 144 foreach ($exts as $key => $val) { 145 $tmp->add($key, is_array($val) ? $val : null); 146 } 147 $exts = $tmp; 148 } elseif (!is_null($exts)) { 149 if ($exts instanceof Horde_Imap_Client_Base) { 150 $exts = $exts->capability; 151 } elseif (!($exts instanceof Horde_Imap_Client_Data_Capability)) { 152 throw new InvalidArgumentException('Incorrect $exts parameter'); 153 } 154 } 155 156 $temp = array( 157 'cmds' => new Horde_Imap_Client_Data_Format_List(), 158 'exts' => $exts, 159 'exts_used' => array() 160 ); 161 $cmds = &$temp['cmds']; 162 $charset = $charset_cname = null; 163 $default_search = true; 164 $exts_used = &$temp['exts_used']; 165 $ptr = &$this->_search; 166 167 $charset_get = function ($c) use (&$charset, &$charset_cname) { 168 $charset = is_null($c) 169 ? 'US-ASCII' 170 : strval($c); 171 $charset_cname = ($charset === 'US-ASCII') 172 ? 'Horde_Imap_Client_Data_Format_Astring' 173 : 'Horde_Imap_Client_Data_Format_Astring_Nonascii'; 174 }; 175 $create_return = function ($charset, $exts_used, $cmds) { 176 return array( 177 'charset' => $charset, 178 'exts' => array_keys(array_flip($exts_used)), 179 'query' => $cmds 180 ); 181 }; 182 183 /* Do IDs check first. If there is an empty ID query (without a NOT 184 * qualifier), the rest of this query is irrelevant since we already 185 * know the search will return no results. */ 186 if (isset($ptr['ids'])) { 187 if (!count($ptr['ids']['ids']) && !$ptr['ids']['ids']->special) { 188 if (empty($ptr['ids']['not'])) { 189 /* This is a match on an empty list of IDs. We do need to 190 * process any OR queries that may exist, since they are 191 * independent of this result. */ 192 if (isset($ptr['or'])) { 193 $this->_buildAndOr( 194 'OR', $ptr['or'], $charset, $exts_used, $cmds 195 ); 196 } 197 return $create_return($charset, $exts_used, $cmds); 198 } 199 200 /* If reached here, this a NOT search of an empty list. We can 201 * safely discard this from the output. */ 202 } else { 203 $this->_addFuzzy(!empty($ptr['ids']['fuzzy']), $temp); 204 if (!empty($ptr['ids']['not'])) { 205 $cmds->add('NOT'); 206 } 207 if (!$ptr['ids']['ids']->sequence) { 208 $cmds->add('UID'); 209 } 210 $cmds->add(strval($ptr['ids']['ids'])); 211 } 212 } 213 214 if (isset($ptr['new'])) { 215 $this->_addFuzzy(!empty($ptr['newfuzzy']), $temp); 216 if ($ptr['new']) { 217 $cmds->add('NEW'); 218 unset($ptr['flag']['UNSEEN']); 219 } else { 220 $cmds->add('OLD'); 221 } 222 unset($ptr['flag']['RECENT']); 223 } 224 225 if (!empty($ptr['flag'])) { 226 foreach ($ptr['flag'] as $key => $val) { 227 $this->_addFuzzy(!empty($val['fuzzy']), $temp); 228 229 $tmp = ''; 230 if (empty($val['set'])) { 231 // This is a 'NOT' search. All system flags but \Recent 232 // have 'UN' equivalents. 233 if ($key == 'RECENT') { 234 $cmds->add('NOT'); 235 } else { 236 $tmp = 'UN'; 237 } 238 } 239 240 if ($val['type'] == 'keyword') { 241 $cmds->add(array( 242 $tmp . 'KEYWORD', 243 $key 244 )); 245 } else { 246 $cmds->add($tmp . $key); 247 } 248 } 249 } 250 251 if (!empty($ptr['header'])) { 252 /* The list of 'system' headers that have a specific search 253 * query. */ 254 $systemheaders = array( 255 'BCC', 'CC', 'FROM', 'SUBJECT', 'TO' 256 ); 257 258 foreach ($ptr['header'] as $val) { 259 $this->_addFuzzy(!empty($val['fuzzy']), $temp); 260 261 if (!empty($val['not'])) { 262 $cmds->add('NOT'); 263 } 264 265 if (in_array($val['header'], $systemheaders)) { 266 $cmds->add($val['header']); 267 } else { 268 $cmds->add(array( 269 'HEADER', 270 new Horde_Imap_Client_Data_Format_Astring($val['header']) 271 )); 272 } 273 274 $charset_get($this->_charset); 275 $cmds->add( 276 new $charset_cname(isset($val['text']) ? $val['text'] : '') 277 ); 278 } 279 } 280 281 if (!empty($ptr['text'])) { 282 foreach ($ptr['text'] as $val) { 283 $this->_addFuzzy(!empty($val['fuzzy']), $temp); 284 285 if (!empty($val['not'])) { 286 $cmds->add('NOT'); 287 } 288 289 $charset_get($this->_charset); 290 $cmds->add(array( 291 $val['type'], 292 new $charset_cname($val['text']) 293 )); 294 } 295 } 296 297 if (!empty($ptr['size'])) { 298 foreach ($ptr['size'] as $key => $val) { 299 $this->_addFuzzy(!empty($val['fuzzy']), $temp); 300 if (!empty($val['not'])) { 301 $cmds->add('NOT'); 302 } 303 $cmds->add(array( 304 $key, 305 new Horde_Imap_Client_Data_Format_Number( 306 empty($val['size']) ? 0 : $val['size'] 307 ) 308 )); 309 } 310 } 311 312 if (!empty($ptr['date'])) { 313 foreach ($ptr['date'] as $val) { 314 $this->_addFuzzy(!empty($val['fuzzy']), $temp); 315 316 if (!empty($val['not'])) { 317 $cmds->add('NOT'); 318 } 319 320 if (empty($val['header'])) { 321 $cmds->add($val['range']); 322 } else { 323 $cmds->add('SENT' . $val['range']); 324 } 325 $cmds->add($val['date']); 326 } 327 } 328 329 if (!empty($ptr['within'])) { 330 if (is_null($exts) || $exts->query('WITHIN')) { 331 $exts_used[] = 'WITHIN'; 332 } 333 334 foreach ($ptr['within'] as $key => $val) { 335 $this->_addFuzzy(!empty($val['fuzzy']), $temp); 336 if (!empty($val['not'])) { 337 $cmds->add('NOT'); 338 } 339 340 if (is_null($exts) || $exts->query('WITHIN')) { 341 $cmds->add(array( 342 $key, 343 new Horde_Imap_Client_Data_Format_Number($val['interval']) 344 )); 345 } else { 346 // This workaround is only accurate to within 1 day, due 347 // to limitations with the IMAP4rev1 search commands. 348 $cmds->add(array( 349 ($key == self::INTERVAL_OLDER) ? self::DATE_BEFORE : self::DATE_SINCE, 350 new Horde_Imap_Client_Data_Format_Date('now -' . $val['interval'] . ' seconds') 351 )); 352 } 353 } 354 } 355 356 if (!empty($ptr['modseq'])) { 357 if (!is_null($exts) && !$exts->query('CONDSTORE')) { 358 throw new Horde_Imap_Client_Exception_NoSupportExtension('CONDSTORE'); 359 } 360 361 $exts_used[] = 'CONDSTORE'; 362 363 $this->_addFuzzy(!empty($ptr['modseq']['fuzzy']), $temp); 364 365 if (!empty($ptr['modseq']['not'])) { 366 $cmds->add('NOT'); 367 } 368 $cmds->add('MODSEQ'); 369 if (isset($ptr['modseq']['name'])) { 370 $cmds->add(array( 371 new Horde_Imap_Client_Data_Format_String($ptr['modseq']['name']), 372 $ptr['modseq']['type'] 373 )); 374 } 375 $cmds->add(new Horde_Imap_Client_Data_Format_Number($ptr['modseq']['value'])); 376 } 377 378 if (isset($ptr['prevsearch'])) { 379 if (!is_null($exts) && !$exts->query('SEARCHRES')) { 380 throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCHRES'); 381 } 382 383 $exts_used[] = 'SEARCHRES'; 384 385 $this->_addFuzzy(!empty($ptr['prevsearchfuzzy']), $temp); 386 387 if (!$ptr['prevsearch']) { 388 $cmds->add('NOT'); 389 } 390 $cmds->add('$'); 391 } 392 393 // Add AND'ed queries 394 if (!empty($ptr['and'])) { 395 $default_search = $this->_buildAndOr( 396 'AND', $ptr['and'], $charset, $exts_used, $cmds 397 ); 398 } 399 400 // Add OR'ed queries 401 if (!empty($ptr['or'])) { 402 $default_search = $this->_buildAndOr( 403 'OR', $ptr['or'], $charset, $exts_used, $cmds 404 ); 405 } 406 407 // Default search is 'ALL' 408 if ($default_search && !count($cmds)) { 409 $cmds->add('ALL'); 410 } 411 412 return $create_return($charset, $exts_used, $cmds); 413 } 414 415 /** 416 * Builds the AND/OR query. 417 * 418 * @param string $type 'AND' or 'OR'. 419 * @param array $data Query data. 420 * @param string &$charset Search charset. 421 * @param array &$exts_used IMAP extensions used. 422 * @param Horde_Imap_Client_Data_Format_List &$cmds Command list. 423 * 424 * @return boolean True if query might return results. 425 */ 426 protected function _buildAndOr($type, $data, &$charset, &$exts_used, 427 &$cmds) 428 { 429 $results = false; 430 431 foreach ($data as $val) { 432 $ret = $val->build(); 433 434 /* Empty sub-query. */ 435 if (!count($ret['query'])) { 436 switch ($type) { 437 case 'AND': 438 /* Any empty sub-query means that the query MUST return 439 * no results. */ 440 $cmds = new Horde_Imap_Client_Data_Format_List(); 441 $exts_used = array(); 442 return false; 443 444 case 'OR': 445 /* Skip this query. */ 446 continue 2; 447 } 448 } 449 450 $results = true; 451 452 if (!is_null($ret['charset']) && ($ret['charset'] != 'US-ASCII')) { 453 if (!is_null($charset) && 454 ($charset != 'US-ASCII') && 455 ($charset != $ret['charset'])) { 456 throw new InvalidArgumentException( 457 'AND/OR queries must all have the same charset.' 458 ); 459 } 460 $charset = $ret['charset']; 461 } 462 463 $exts_used = array_merge($exts_used, $ret['exts']); 464 465 switch ($type) { 466 case 'AND': 467 $cmds->add($ret['query'], true); 468 break; 469 470 case 'OR': 471 // First OR'd query 472 if (count($cmds)) { 473 $new_cmds = new Horde_Imap_Client_Data_Format_List(); 474 $new_cmds->add(array( 475 'OR', 476 $ret['query'], 477 $cmds 478 )); 479 $cmds = $new_cmds; 480 } else { 481 $cmds = $ret['query']; 482 } 483 break; 484 } 485 } 486 487 return $results; 488 } 489 490 /** 491 * Adds fuzzy modifier to search keys. 492 * 493 * @param boolean $add Add the fuzzy modifier? 494 * @param array $temp Temporary build data. 495 * 496 * @throws Horde_Imap_Client_Exception_NoSupport_Extension 497 */ 498 protected function _addFuzzy($add, &$temp) 499 { 500 if ($add) { 501 if (!$temp['exts']->query('SEARCH', 'FUZZY')) { 502 throw new Horde_Imap_Client_Exception_NoSupportExtension('SEARCH=FUZZY'); 503 } 504 $temp['cmds']->add('FUZZY'); 505 $temp['exts_used'][] = 'SEARCH=FUZZY'; 506 } 507 } 508 509 /** 510 * Search for a flag/keywords. 511 * 512 * @param string $name The flag or keyword name. 513 * @param boolean $set If true, search for messages that have the flag 514 * set. If false, search for messages that do not 515 * have the flag set. 516 * @param array $opts Additional options: 517 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 518 * MUST support RFC 6203. 519 */ 520 public function flag($name, $set = true, array $opts = array()) 521 { 522 $name = Horde_String::upper(ltrim($name, '\\')); 523 if (!isset($this->_search['flag'])) { 524 $this->_search['flag'] = array(); 525 } 526 527 /* The list of defined system flags (see RFC 3501 [2.3.2]). */ 528 $systemflags = array( 529 'ANSWERED', 'DELETED', 'DRAFT', 'FLAGGED', 'RECENT', 'SEEN' 530 ); 531 532 $this->_search['flag'][$name] = array_filter(array( 533 'fuzzy' => !empty($opts['fuzzy']), 534 'set' => $set, 535 'type' => in_array($name, $systemflags) ? 'flag' : 'keyword' 536 )); 537 } 538 539 /** 540 * Determines if flags are a part of the search. 541 * 542 * @return boolean True if search query involves flags. 543 */ 544 public function flagSearch() 545 { 546 return !empty($this->_search['flag']); 547 } 548 549 /** 550 * Search for either new messages (messages that have the '\Recent' flag 551 * but not the '\Seen' flag) or old messages (messages that do not have 552 * the '\Recent' flag). If new messages are searched, this will clear 553 * any '\Recent' or '\Unseen' flag searches. If old messages are searched, 554 * this will clear any '\Recent' flag search. 555 * 556 * @param boolean $newmsgs If true, searches for new messages. Else, 557 * search for old messages. 558 * @param array $opts Additional options: 559 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 560 * MUST support RFC 6203. 561 */ 562 public function newMsgs($newmsgs = true, array $opts = array()) 563 { 564 $this->_search['new'] = $newmsgs; 565 if (!empty($opts['fuzzy'])) { 566 $this->_search['newfuzzy'] = true; 567 } 568 } 569 570 /** 571 * Search for text in the header of a message. 572 * 573 * @param string $header The header field. 574 * @param string $text The search text. 575 * @param boolean $not If true, do a 'NOT' search of $text. 576 * @param array $opts Additional options: 577 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 578 * MUST support RFC 6203. 579 */ 580 public function headerText($header, $text, $not = false, 581 array $opts = array()) 582 { 583 if (!isset($this->_search['header'])) { 584 $this->_search['header'] = array(); 585 } 586 $this->_search['header'][] = array_filter(array( 587 'fuzzy' => !empty($opts['fuzzy']), 588 'header' => Horde_String::upper($header), 589 'text' => $text, 590 'not' => $not 591 )); 592 } 593 594 /** 595 * Search for text in either the entire message, or just the body. 596 * 597 * @param string $text The search text. 598 * @param string $bodyonly If true, only search in the body of the 599 * message. If false, also search in the headers. 600 * @param boolean $not If true, do a 'NOT' search of $text. 601 * @param array $opts Additional options: 602 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 603 * MUST support RFC 6203. 604 */ 605 public function text($text, $bodyonly = true, $not = false, 606 array $opts = array()) 607 { 608 if (!isset($this->_search['text'])) { 609 $this->_search['text'] = array(); 610 } 611 612 $this->_search['text'][] = array_filter(array( 613 'fuzzy' => !empty($opts['fuzzy']), 614 'not' => $not, 615 'text' => $text, 616 'type' => $bodyonly ? 'BODY' : 'TEXT' 617 )); 618 } 619 620 /** 621 * Search for messages smaller/larger than a certain size. 622 * 623 * @todo: Remove $not for 3.0 624 * 625 * @param integer $size The size (in bytes). 626 * @param boolean $larger Search for messages larger than $size? 627 * @param boolean $not If true, do a 'NOT' search of $text. 628 * @param array $opts Additional options: 629 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 630 * MUST support RFC 6203. 631 */ 632 public function size($size, $larger = false, $not = false, 633 array $opts = array()) 634 { 635 if (!isset($this->_search['size'])) { 636 $this->_search['size'] = array(); 637 } 638 $this->_search['size'][$larger ? 'LARGER' : 'SMALLER'] = array_filter(array( 639 'fuzzy' => !empty($opts['fuzzy']), 640 'not' => $not, 641 'size' => (float)$size 642 )); 643 } 644 645 /** 646 * Search for messages within a given UID range. Only one message range 647 * can be specified per query. 648 * 649 * @param Horde_Imap_Client_Ids $ids The list of UIDs to search. 650 * @param boolean $not If true, do a 'NOT' search of the 651 * UIDs. 652 * @param array $opts Additional options: 653 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 654 * MUST support RFC 6203. 655 */ 656 public function ids(Horde_Imap_Client_Ids $ids, $not = false, 657 array $opts = array()) 658 { 659 $this->_search['ids'] = array_filter(array( 660 'fuzzy' => !empty($opts['fuzzy']), 661 'ids' => $ids, 662 'not' => $not 663 )); 664 } 665 666 /** 667 * Search for messages within a date range. 668 * 669 * @param mixed $date DateTime or Horde_Date object. 670 * @param string $range Either: 671 * - Horde_Imap_Client_Search_Query::DATE_BEFORE 672 * - Horde_Imap_Client_Search_Query::DATE_ON 673 * - Horde_Imap_Client_Search_Query::DATE_SINCE 674 * @param boolean $header If true, search using the date in the message 675 * headers. If false, search using the internal 676 * IMAP date (usually arrival time). 677 * @param boolean $not If true, do a 'NOT' search of the range. 678 * @param array $opts Additional options: 679 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 680 * MUST support RFC 6203. 681 */ 682 public function dateSearch($date, $range, $header = true, $not = false, 683 array $opts = array()) 684 { 685 if (!isset($this->_search['date'])) { 686 $this->_search['date'] = array(); 687 } 688 689 // We should really be storing the raw DateTime object as data, 690 // but all versions of the query object have converted at this stage. 691 $ob = new Horde_Imap_Client_Data_Format_Date($date); 692 693 $this->_search['date'][] = array_filter(array( 694 'date' => $ob->escape(), 695 'fuzzy' => !empty($opts['fuzzy']), 696 'header' => $header, 697 'range' => $range, 698 'not' => $not 699 )); 700 } 701 702 /** 703 * Search for messages within a date and time range. 704 * 705 * @param mixed $date DateTime or Horde_Date object. 706 * @param string $range Either: 707 * - Horde_Imap_Client_Search_Query::DATE_BEFORE 708 * - Horde_Imap_Client_Search_Query::DATE_ON 709 * - Horde_Imap_Client_Search_Query::DATE_SINCE 710 * @param boolean $header If true, search using the date in the message 711 * headers. If false, search using the internal 712 * IMAP date (usually arrival time). 713 * @param boolean $not If true, do a 'NOT' search of the range. 714 * @param array $opts Additional options: 715 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 716 * MUST support RFC 6203. 717 */ 718 public function dateTimeSearch($date, $range, $header = true, $not = false, 719 array $opts = array()) 720 { 721 if (!isset($this->_search['date'])) { 722 $this->_search['date'] = array(); 723 } 724 725 // We should really be storing the raw DateTime object as data, 726 // but all versions of the query object have converted at this stage. 727 $ob = new Horde_Imap_Client_Data_Format_DateTime($date); 728 729 $this->_search['date'][] = array_filter(array( 730 'date' => $ob->escape(), 731 'fuzzy' => !empty($opts['fuzzy']), 732 'header' => $header, 733 'range' => $range, 734 'not' => $not 735 )); 736 } 737 738 /** 739 * Search for messages within a given interval. Only one interval of each 740 * type can be specified per search query. If the IMAP server supports 741 * the WITHIN extension (RFC 5032), it will be used. Otherwise, the 742 * search query will be dynamically created using IMAP4rev1 search 743 * terms. 744 * 745 * @param integer $interval Seconds from the present. 746 * @param string $range Either: 747 * - Horde_Imap_Client_Search_Query::INTERVAL_OLDER 748 * - Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER 749 * @param boolean $not If true, do a 'NOT' search. 750 * @param array $opts Additional options: 751 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 752 * MUST support RFC 6203. 753 */ 754 public function intervalSearch($interval, $range, $not = false, 755 array $opts = array()) 756 { 757 if (!isset($this->_search['within'])) { 758 $this->_search['within'] = array(); 759 } 760 $this->_search['within'][$range] = array( 761 'fuzzy' => !empty($opts['fuzzy']), 762 'interval' => $interval, 763 'not' => $not 764 ); 765 } 766 767 /** 768 * AND queries - the contents of this query will be AND'ed (in its 769 * entirety) with the contents of EACH of the queries passed in. All 770 * AND'd queries must share the same charset as this query. 771 * 772 * @param mixed $queries A query, or an array of queries, to AND with the 773 * current query. 774 */ 775 public function andSearch($queries) 776 { 777 if (!isset($this->_search['and'])) { 778 $this->_search['and'] = array(); 779 } 780 781 if ($queries instanceof Horde_Imap_Client_Search_Query) { 782 $queries = array($queries); 783 } 784 785 $this->_search['and'] = array_merge($this->_search['and'], $queries); 786 } 787 788 /** 789 * OR a query - the contents of this query will be OR'ed (in its entirety) 790 * with the contents of EACH of the queries passed in. All OR'd queries 791 * must share the same charset as this query. All contents of any single 792 * query will be AND'ed together. 793 * 794 * @param mixed $queries A query, or an array of queries, to OR with the 795 * current query. 796 */ 797 public function orSearch($queries) 798 { 799 if (!isset($this->_search['or'])) { 800 $this->_search['or'] = array(); 801 } 802 803 if ($queries instanceof Horde_Imap_Client_Search_Query) { 804 $queries = array($queries); 805 } 806 807 $this->_search['or'] = array_merge($this->_search['or'], $queries); 808 } 809 810 /** 811 * Search for messages modified since a specific moment. The IMAP server 812 * must support the CONDSTORE extension (RFC 7162) for this query to be 813 * used. 814 * 815 * @param integer $value The mod-sequence value. 816 * @param string $name The entry-name string. 817 * @param string $type Either 'shared', 'priv', or 'all'. Defaults to 818 * 'all' 819 * @param boolean $not If true, do a 'NOT' search. 820 * @param array $opts Additional options: 821 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 822 * MUST support RFC 6203. 823 */ 824 public function modseq($value, $name = null, $type = null, $not = false, 825 array $opts = array()) 826 { 827 if (!is_null($type)) { 828 $type = Horde_String::lower($type); 829 if (!in_array($type, array('shared', 'priv', 'all'))) { 830 $type = 'all'; 831 } 832 } 833 834 $this->_search['modseq'] = array_filter(array( 835 'fuzzy' => !empty($opts['fuzzy']), 836 'name' => $name, 837 'not' => $not, 838 'type' => (!is_null($name) && is_null($type)) ? 'all' : $type, 839 'value' => $value 840 )); 841 } 842 843 /** 844 * Use the results from the previous SEARCH command. The IMAP server must 845 * support the SEARCHRES extension (RFC 5182) for this query to be used. 846 * 847 * @param boolean $not If true, don't match the previous query. 848 * @param array $opts Additional options: 849 * - fuzzy: (boolean) If true, perform a fuzzy search. The IMAP server 850 * MUST support RFC 6203. 851 */ 852 public function previousSearch($not = false, array $opts = array()) 853 { 854 $this->_search['prevsearch'] = $not; 855 if (!empty($opts['fuzzy'])) { 856 $this->_search['prevsearchfuzzy'] = true; 857 } 858 } 859 860 /* Serializable methods. */ 861 862 /** 863 * Serialization. 864 * 865 * @return string Serialized data. 866 */ 867 public function serialize() 868 { 869 $data = array( 870 // Serialized data ID. 871 self::VERSION, 872 $this->_search 873 ); 874 875 if (!is_null($this->_charset)) { 876 $data[] = $this->_charset; 877 } 878 879 return serialize($data); 880 } 881 882 /** 883 * Unserialization. 884 * 885 * @param string $data Serialized data. 886 * 887 * @throws Exception 888 */ 889 public function unserialize($data) 890 { 891 $data = @unserialize($data); 892 if (!is_array($data) || 893 !isset($data[0]) || 894 ($data[0] != self::VERSION)) { 895 throw new Exception('Cache version change'); 896 } 897 898 $this->_search = $data[1]; 899 if (isset($data[2])) { 900 $this->_charset = $data[2]; 901 } 902 } 903 904 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body