PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.1
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Libraries/Imap2/Roundcube/ResultThread.php +0 -664 10.2.2 → 11.1.1 View file →
@@ -1,664 +1,0 @@
1 -<?php
2 -
3 -/**
4 - * +-----------------------------------------------------------------------+
5 - * | This file is part of the Roundcube Webmail client |
6 - * | |
7 - * | Copyright (C) The Roundcube Dev Team |
8 - * | Copyright (C) Kolab Systems AG |
9 - * | |
10 - * | Licensed under the GNU General Public License version 3 or |
11 - * | any later version with exceptions for skins & plugins. |
12 - * | See the README file for a full license statement. |
13 - * | |
14 - * | PURPOSE: |
15 - * | THREAD response handler |
16 - * +-----------------------------------------------------------------------+
17 - * | Author: Thomas Bruederli <[email protected]> |
18 - * | Author: Aleksander Machniak <[email protected]> |
19 - * +-----------------------------------------------------------------------+
20 - */
21 -
22 -namespace AcyMailing\Libraries\Imap2\Roundcube;
23 -
24 -use AcyMailing\Libraries\Imap2\Offset;
25 -use AcyMailing\Libraries\Imap2\rcube_imap_generic;
26 -use AcyMailing\Libraries\Imap2\rcube_result_index;
27 -
28 -/**
29 - * Class for accessing IMAP's THREAD result
30 - *
31 - * @package Framework
32 - * @subpackage Storage
33 - */
34 -class ResultThread
35 -{
36 - public $incomplete = false;
37 -
38 - protected $raw_data;
39 - protected $mailbox;
40 - protected $meta = [];
41 - protected $order = 'ASC';
42 -
43 - const SEPARATOR_ELEMENT = ' ';
44 - const SEPARATOR_ITEM = '~';
45 - const SEPARATOR_LEVEL = ':';
46 -
47 -
48 - /**
49 - * Object constructor.
50 - */
51 - public function __construct($mailbox = null, $data = null)
52 - {
53 - $this->mailbox = $mailbox;
54 - $this->init($data);
55 - }
56 -
57 - /**
58 - * Initializes object with IMAP command response
59 - *
60 - * @param string $data IMAP response string
61 - */
62 - public function init($data = null)
63 - {
64 - $this->meta = [];
65 -
66 - $data = explode('*', (string)$data);
67 -
68 - // ...skip unilateral untagged server responses
69 - for ($i = 0, $len = count($data) ; $i < $len ; $i++) {
70 - if (preg_match('/^ THREAD/i', $data[$i])) {
71 - // valid response, initialize raw_data for is_error()
72 - $this->raw_data = '';
73 - $data[$i] = substr($data[$i], 7);
74 - break;
75 - }
76 -
77 - unset($data[$i]);
78 - }
79 -
80 - if (empty($data)) {
81 - return;
82 - }
83 -
84 - $data = array_shift($data);
85 - $data = trim($data);
86 - $data = preg_replace('/[\r\n]/', '', $data);
87 - $data = preg_replace('/\s+/', ' ', $data);
88 -
89 - $this->raw_data = $this->parse_thread($data);
90 - }
91 -
92 - /**
93 - * Checks the result from IMAP command
94 - *
95 - * @return bool True if the result is an error, False otherwise
96 - */
97 - public function is_error()
98 - {
99 - return $this->raw_data === null;
100 - }
101 -
102 - /**
103 - * Checks if the result is empty
104 - *
105 - * @return bool True if the result is empty, False otherwise
106 - */
107 - public function is_empty()
108 - {
109 - return empty($this->raw_data);
110 - }
111 -
112 - /**
113 - * Returns number of elements (threads) in the result
114 - *
115 - * @return int Number of elements
116 - */
117 - public function count()
118 - {
119 - if (isset($this->meta['count']) && $this->meta['count'] !== null) {
120 - return $this->meta['count'];
121 - }
122 -
123 - if (empty($this->raw_data)) {
124 - $this->meta['count'] = 0;
125 - } else {
126 - $this->meta['count'] = 1 + substr_count($this->raw_data, self::SEPARATOR_ELEMENT);
127 - }
128 -
129 - if (!$this->meta['count']) {
130 - $this->meta['messages'] = 0;
131 - }
132 -
133 - return $this->meta['count'];
134 - }
135 -
136 - /**
137 - * Returns number of all messages in the result
138 - *
139 - * @return int Number of elements
140 - */
141 - public function count_messages()
142 - {
143 - if ($this->meta['messages'] !== null) {
144 - return $this->meta['messages'];
145 - }
146 -
147 - if (empty($this->raw_data)) {
148 - $this->meta['messages'] = 0;
149 - } else {
150 - $this->meta['messages'] = 1
151 - + substr_count($this->raw_data, self::SEPARATOR_ELEMENT)
152 - + substr_count($this->raw_data, self::SEPARATOR_ITEM);
153 - }
154 -
155 - if ($this->meta['messages'] == 0 || $this->meta['messages'] == 1) {
156 - $this->meta['count'] = $this->meta['messages'];
157 - }
158 -
159 - return $this->meta['messages'];
160 - }
161 -
162 - /**
163 - * Returns maximum message identifier in the result
164 - *
165 - * @return int Maximum message identifier
166 - */
167 - public function max()
168 - {
169 - if (!isset($this->meta['max'])) {
170 - $this->meta['max'] = (int)@max($this->get());
171 - }
172 -
173 - return $this->meta['max'];
174 - }
175 -
176 - /**
177 - * Returns minimum message identifier in the result
178 - *
179 - * @return int Minimum message identifier
180 - */
181 - public function min()
182 - {
183 - if (!isset($this->meta['min'])) {
184 - $this->meta['min'] = (int)@min($this->get());
185 - }
186 -
187 - return $this->meta['min'];
188 - }
189 -
190 - /**
191 - * Slices data set.
192 - *
193 - * @param $offset Offset (as for PHP's array_slice())
194 - * @param $length Number of elements (as for PHP's array_slice())
195 - */
196 - public function slice($offset, $length)
197 - {
198 - $data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
199 - $data = array_slice($data, $offset, $length);
200 -
201 - $this->meta = [];
202 - $this->meta['count'] = count($data);
203 - $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
204 - }
205 -
206 - /**
207 - * Filters data set. Removes threads not listed in $roots list.
208 - *
209 - * @param array $roots List of IDs of thread roots.
210 - */
211 - public function filter($roots)
212 - {
213 - $datalen = strlen($this->raw_data);
214 - $roots = array_flip($roots);
215 - $result = '';
216 - $start = 0;
217 -
218 - $this->meta = [];
219 - $this->meta['count'] = 0;
220 -
221 - while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
222 - || ($start < $datalen && ($pos = $datalen))
223 - ) {
224 - $len = $pos - $start;
225 - $elem = substr($this->raw_data, $start, $len);
226 - $start = $pos + 1;
227 -
228 - // extract root message ID
229 - if ($npos = strpos($elem, self::SEPARATOR_ITEM)) {
230 - $root = (int)substr($elem, 0, $npos);
231 - } else {
232 - $root = $elem;
233 - }
234 -
235 - if (isset($roots[$root])) {
236 - $this->meta['count']++;
237 - $result .= self::SEPARATOR_ELEMENT.$elem;
238 - }
239 - }
240 -
241 - $this->raw_data = ltrim($result, self::SEPARATOR_ELEMENT);
242 - }
243 -
244 - /**
245 - * Reverts order of elements in the result
246 - */
247 - public function revert()
248 - {
249 - $this->order = $this->order == 'ASC' ? 'DESC' : 'ASC';
250 -
251 - if (empty($this->raw_data)) {
252 - return;
253 - }
254 -
255 - $data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
256 - $data = array_reverse($data);
257 - $this->raw_data = implode(self::SEPARATOR_ELEMENT, $data);
258 -
259 - $this->meta['pos'] = [];
260 - }
261 -
262 - /**
263 - * Check if the given message ID exists in the object
264 - *
265 - * @param int $msgid Message ID
266 - * @param bool $get_index When enabled element's index will be returned.
267 - * Elements are indexed starting with 0
268 - *
269 - * @return boolean True on success, False if message ID doesn't exist
270 - */
271 - public function exists($msgid, $get_index = false)
272 - {
273 - $msgid = (int)$msgid;
274 - $begin = implode('|', [
275 - '^',
276 - preg_quote(self::SEPARATOR_ELEMENT, '/'),
277 - preg_quote(self::SEPARATOR_LEVEL, '/'),
278 - ]);
279 - $end = implode('|', [
280 - '$',
281 - preg_quote(self::SEPARATOR_ELEMENT, '/'),
282 - preg_quote(self::SEPARATOR_ITEM, '/'),
283 - ]);
284 -
285 - if (preg_match(
286 - "/($begin)$msgid($end)/",
287 - $this->raw_data,
288 - $m,
289 - $get_index ? PREG_OFFSET_CAPTURE : null
290 - )
291 - ) {
292 - if ($get_index) {
293 - $idx = 0;
294 - if ($m[0][1]) {
295 - $idx = substr_count($this->raw_data, self::SEPARATOR_ELEMENT, 0, $m[0][1] + 1)
296 - + substr_count($this->raw_data, self::SEPARATOR_ITEM, 0, $m[0][1] + 1);
297 - }
298 - // cache position of this element, so we can use it in get_element()
299 - $this->meta['pos'][$idx] = (int)$m[0][1];
300 -
301 - return $idx;
302 - }
303 -
304 - return true;
305 - }
306 -
307 - return false;
308 - }
309 -
310 - /**
311 - * Return IDs of all messages in the result. Threaded data will be flattened.
312 - *
313 - * @return array List of message identifiers
314 - */
315 - public function get()
316 - {
317 - if (empty($this->raw_data)) {
318 - return [];
319 - }
320 -
321 - $regexp = '/('.preg_quote(self::SEPARATOR_ELEMENT, '/')
322 - .'|'.preg_quote(self::SEPARATOR_ITEM, '/').'[0-9]+'.preg_quote(self::SEPARATOR_LEVEL, '/')
323 - .')/';
324 -
325 - return preg_split($regexp, $this->raw_data);
326 - }
327 -
328 - /**
329 - * Return all messages in the result.
330 - *
331 - * @return array List of message identifiers
332 - */
333 - public function get_compressed()
334 - {
335 - if (empty($this->raw_data)) {
336 - return '';
337 - }
338 -
339 - return rcube_imap_generic::compressMessageSet($this->get());
340 - }
341 -
342 - /**
343 - * Return result element at specified index (all messages, not roots)
344 - *
345 - * @param int|string $index Element's index or "FIRST" or "LAST"
346 - *
347 - * @return int Element value
348 - */
349 - public function get_element($index)
350 - {
351 - $count = $this->count();
352 -
353 - if (!$count) {
354 - return null;
355 - }
356 -
357 - // first element
358 - if ($index === 0 || $index === '0' || $index === 'FIRST') {
359 - preg_match('/^([0-9]+)/', $this->raw_data, $m);
360 - $result = (int)$m[1];
361 -
362 - return $result;
363 - }
364 -
365 - // last element
366 - if ($index === 'LAST' || $index == $count - 1) {
367 - preg_match('/([0-9]+)$/', $this->raw_data, $m);
368 - $result = (int)$m[1];
369 -
370 - return $result;
371 - }
372 -
373 - // do we know the position of the element or the neighbour of it?
374 - if (!empty($this->meta['pos'])) {
375 - $element = preg_quote(self::SEPARATOR_ELEMENT, '/');
376 - $item = preg_quote(self::SEPARATOR_ITEM, '/').'[0-9]+'.preg_quote(self::SEPARATOR_LEVEL, '/').'?';
377 - $regexp = '('.$element.'|'.$item.')';
378 -
379 - if (isset($this->meta['pos'][$index])) {
380 - if (preg_match('/([0-9]+)/', $this->raw_data, $m, null, $this->meta['pos'][$index])) {
381 - $result = $m[1];
382 - }
383 - } elseif (isset($this->meta['pos'][$index - 1])) {
384 - // get chunk of data after previous element
385 - $data = substr($this->raw_data, $this->meta['pos'][$index - 1] + 1, 50);
386 - $data = preg_replace('/^[0-9]+/', '', $data); // remove UID at $index position
387 - $data = preg_replace("/^$regexp/", '', $data); // remove separator
388 - if (preg_match('/^([0-9]+)/', $data, $m)) {
389 - $result = $m[1];
390 - }
391 - } elseif (isset($this->meta['pos'][$index + 1])) {
392 - // get chunk of data before next element
393 - $pos = max(0, $this->meta['pos'][$index + 1] - 50);
394 - $len = min(50, $this->meta['pos'][$index + 1]);
395 - $data = substr($this->raw_data, $pos, $len);
396 - $data = preg_replace("/$regexp\$/", '', $data); // remove separator
397 -
398 - if (preg_match('/([0-9]+)$/', $data, $m)) {
399 - $result = $m[1];
400 - }
401 - }
402 -
403 - if (isset($result)) {
404 - return (int)$result;
405 - }
406 - }
407 -
408 - // Finally use less effective method
409 - $data = $this->get();
410 -
411 - return $data[$index];
412 - }
413 -
414 - /**
415 - * Returns response parameters e.g. MAILBOX, ORDER
416 - *
417 - * @param string $param Parameter name
418 - *
419 - * @return array|string Response parameters or parameter value
420 - */
421 - public function get_parameters($param = null)
422 - {
423 - $params = [];
424 - $params['MAILBOX'] = $this->mailbox;
425 - $params['ORDER'] = $this->order;
426 -
427 - if ($param !== null) {
428 - return $params[$param];
429 - }
430 -
431 - return $params;
432 - }
433 -
434 - /**
435 - * THREAD=REFS sorting implementation (based on provided index)
436 - *
437 - * @param rcube_result_index $index Sorted message identifiers
438 - */
439 - public function sort($index)
440 - {
441 - $this->sort_order = $index->get_parameters('ORDER');
442 -
443 - if (empty($this->raw_data)) {
444 - return;
445 - }
446 -
447 - // when sorting search result it's good to make the index smaller
448 - if ($index->count() != $this->count_messages()) {
449 - $index->filter($this->get());
450 - }
451 -
452 - $result = array_fill_keys($index->get(), null);
453 - $datalen = strlen($this->raw_data);
454 - $start = 0;
455 -
456 - // Here we're parsing raw_data twice, we want only one big array
457 - // in memory at a time
458 -
459 - // Assign roots
460 - while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
461 - || ($start < $datalen && ($pos = $datalen))
462 - ) {
463 - $len = $pos - $start;
464 - $elem = substr($this->raw_data, $start, $len);
465 - $start = $pos + 1;
466 -
467 - $items = explode(self::SEPARATOR_ITEM, $elem);
468 - $root = (int)array_shift($items);
469 -
470 - if ($root) {
471 - $result[$root] = $root;
472 - foreach ($items as $item) {
473 - [$lv, $id] = explode(self::SEPARATOR_LEVEL, $item);
474 - $result[$id] = $root;
475 - }
476 - }
477 - }
478 -
479 - // get only unique roots
480 - $result = array_filter($result); // make sure there are no nulls
481 - $result = array_unique($result);
482 -
483 - // Re-sort raw data
484 - $result = array_fill_keys($result, null);
485 - $start = 0;
486 -
487 - while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
488 - || ($start < $datalen && ($pos = $datalen))
489 - ) {
490 - $len = $pos - $start;
491 - $elem = substr($this->raw_data, $start, $len);
492 - $start = $pos + 1;
493 -
494 - $npos = strpos($elem, self::SEPARATOR_ITEM);
495 - $root = (int)($npos ? substr($elem, 0, $npos) : $elem);
496 -
497 - $result[$root] = $elem;
498 - }
499 -
500 - $this->raw_data = implode(self::SEPARATOR_ELEMENT, $result);
501 - }
502 -
503 - /**
504 - * Returns data as tree
505 - *
506 - * @return array Data tree
507 - */
508 - public function get_tree()
509 - {
510 - $datalen = strlen($this->raw_data);
511 - $result = [];
512 - $start = 0;
513 -
514 - while (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start))
515 - || ($start < $datalen && ($pos = $datalen))
516 - ) {
517 - $len = $pos - $start;
518 - $elem = substr($this->raw_data, $start, $len);
519 - $items = explode(self::SEPARATOR_ITEM, $elem);
520 - $result[array_shift($items)] = $this->build_thread($items);
521 - $start = $pos + 1;
522 - }
523 -
524 - return $result;
525 - }
526 -
527 - /**
528 - * Returns thread depth and children data
529 - *
530 - * @return array Thread data
531 - */
532 - public function get_thread_data()
533 - {
534 - $data = $this->get_tree();
535 - $depth = [];
536 - $children = [];
537 -
538 - $this->build_thread_data($data, $depth, $children);
539 -
540 - return [$depth, $children];
541 - }
542 -
543 - /**
544 - * Creates 'depth' and 'children' arrays from stored thread 'tree' data.
545 - */
546 - protected function build_thread_data($data, &$depth, &$children, $level = 0)
547 - {
548 - foreach ((array)$data as $key => $val) {
549 - $empty = empty($val) || !is_array($val);
550 - $children[$key] = !$empty;
551 - $depth[$key] = $level;
552 - if (!$empty) {
553 - $this->build_thread_data($val, $depth, $children, $level + 1);
554 - }
555 - }
556 - }
557 -
558 - /**
559 - * Converts part of the raw thread into an array
560 - */
561 - protected function build_thread($items, $level = 1, &$pos = 0)
562 - {
563 - $result = [];
564 -
565 - for ($len = count($items) ; $pos < $len ; $pos++) {
566 - [$lv, $id] = explode(self::SEPARATOR_LEVEL, $items[$pos]);
567 - if ($level == $lv) {
568 - $pos++;
569 - $result[$id] = $this->build_thread($items, $level + 1, $pos);
570 - } else {
571 - $pos--;
572 - break;
573 - }
574 - }
575 -
576 - return $result;
577 - }
578 -
579 - /**
580 - * IMAP THREAD response parser
581 - */
582 - protected function parse_thread($str, $begin = 0, $end = 0, $depth = 0)
583 - {
584 - // Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about
585 - // 7 times instead :-) See comments on http://uk2.php.net/references and this article:
586 - // http://derickrethans.nl/files/phparch-php-variables-article.pdf
587 - $node = '';
588 - if (!$end) {
589 - $end = strlen($str);
590 - }
591 -
592 - // Let's try to store data in max. compacted stracture as a string,
593 - // arrays handling is much more expensive
594 - // For the following structure: THREAD (2)(3 6 (4 23)(44 7 96))
595 - // -- 2
596 - // -- 3
597 - // \-- 6
598 - // |-- 4
599 - // | \-- 23
600 - // |
601 - // \-- 44
602 - // \-- 7
603 - // \-- 96
604 - //
605 - // The output will be: 2,3^1:6^2:4^3:23^2:44^3:7^4:96
606 -
607 - if ($str[$begin] != '(') {
608 - // find next bracket
609 - $stop = $begin + strcspn($str, '()', $begin, $end - $begin);
610 - $messages = explode(' ', trim(substr($str, $begin, $stop - $begin)));
611 -
612 - if (empty($messages)) {
613 - return $node;
614 - }
615 -
616 - foreach ($messages as $msg) {
617 - if ($msg) {
618 - $node .= ($depth ? self::SEPARATOR_ITEM.$depth.self::SEPARATOR_LEVEL : '').$msg;
619 - $this->meta['messages']++;
620 - $depth++;
621 - }
622 - }
623 -
624 - if ($stop < $end) {
625 - $node .= $this->parse_thread($str, $stop, $end, $depth);
626 - }
627 - } else {
628 - $off = $begin;
629 - while ($off < $end) {
630 - $start = $off;
631 - $off++;
632 - $n = 1;
633 - while ($n > 0) {
634 - $p = strpos($str, ')', $off);
635 - if ($p === false) {
636 - // error, wrong structure, mismatched brackets in IMAP THREAD response
637 - // @TODO: write error to the log or maybe set $this->raw_data = null;
638 - return $node;
639 - }
640 - $p1 = strpos($str, '(', $off);
641 - if ($p1 !== false && $p1 < $p) {
642 - $off = $p1 + 1;
643 - $n++;
644 - } else {
645 - $off = $p + 1;
646 - $n--;
647 - }
648 - }
649 -
650 - $thread = $this->parse_thread($str, $start + 1, $off - 1, $depth);
651 - if ($thread) {
652 - if (!$depth) {
653 - if ($node) {
654 - $node .= self::SEPARATOR_ELEMENT;
655 - }
656 - }
657 - $node .= $thread;
658 - }
659 - }
660 - }
661 -
662 - return $node;
663 - }
664 -}