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