comparison program/lib/Roundcube/rcube_result_thread.php @ 23:929c05ba3b2f

fixed sort issue?
author Charlie Root
date Fri, 17 Oct 2025 17:31:07 -0400
parents b6a96bdd6b29
children bea5a38be938
comparison
equal deleted inserted replaced
22:303b85d5b561 23:929c05ba3b2f
72 72
73 if (empty($data)) { 73 if (empty($data)) {
74 return; 74 return;
75 } 75 }
76 76
77 $data = array_shift($data); 77 $data = array_first($data);
78 $data = trim($data); 78 $data = trim($data);
79 $data = preg_replace('/[\r\n]/', '', $data); 79 $data = preg_replace('/[\r\n]/', '', $data);
80 $data = preg_replace('/\s+/', ' ', $data); 80 $data = preg_replace('/\s+/', ' ', $data);
81 81
82 $this->raw_data = $this->parse_thread($data); 82 $this->raw_data = empty($data) ? '' : $this->parse_thread($data);
83 } 83 }
84 84
85 /** 85 /**
86 * Checks the result from IMAP command 86 * Checks the result from IMAP command
87 * 87 *
130 * 130 *
131 * @return int Number of elements 131 * @return int Number of elements
132 */ 132 */
133 public function count_messages() 133 public function count_messages()
134 { 134 {
135 if ($this->meta['messages'] !== null) 135 if (!empty($this->meta['messages'])) {
136 return $this->meta['messages']; 136 return $this->meta['messages'];
137 }
137 138
138 if (empty($this->raw_data)) { 139 if (empty($this->raw_data)) {
139 $this->meta['messages'] = 0; 140 $this->meta['messages'] = 0;
140 } 141 }
141 else { 142 else {
151 } 152 }
152 153
153 /** 154 /**
154 * Returns maximum message identifier in the result 155 * Returns maximum message identifier in the result
155 * 156 *
156 * @return int Maximum message identifier 157 * @return int|null Maximum message identifier
157 */ 158 */
158 public function max() 159 public function max()
159 { 160 {
161 if ($this->is_empty()) {
162 return null;
163 }
164
160 if (!isset($this->meta['max'])) { 165 if (!isset($this->meta['max'])) {
161 $this->meta['max'] = (int) @max($this->get()); 166 $this->meta['max'] = (int) @max($this->get());
162 } 167 }
163 return $this->meta['max']; 168 return $this->meta['max'];
164 } 169 }
165 170
166 /** 171 /**
167 * Returns minimum message identifier in the result 172 * Returns minimum message identifier in the result
168 * 173 *
169 * @return int Minimum message identifier 174 * @return int|null Minimum message identifier
170 */ 175 */
171 public function min() 176 public function min()
172 { 177 {
178 if ($this->is_empty()) {
179 return null;
180 }
181
173 if (!isset($this->meta['min'])) { 182 if (!isset($this->meta['min'])) {
174 $this->meta['min'] = (int) @min($this->get()); 183 $this->meta['min'] = (int) @min($this->get());
175 } 184 }
176 return $this->meta['min']; 185 return $this->meta['min'];
177 } 186 }
178 187
179 /** 188 /**
180 * Slices data set. 189 * Slices data set.
181 * 190 *
182 * @param $offset Offset (as for PHP's array_slice()) 191 * @param int $offset Offset (as for PHP's array_slice())
183 * @param $length Number of elements (as for PHP's array_slice()) 192 * @param int $length Number of elements (as for PHP's array_slice())
184 */ 193 */
185 public function slice($offset, $length) 194 public function slice($offset, $length)
186 { 195 {
187 $data = explode(self::SEPARATOR_ELEMENT, $this->raw_data); 196 $data = explode(self::SEPARATOR_ELEMENT, $this->raw_data);
188 $data = array_slice($data, $offset, $length); 197 $data = array_slice($data, $offset, $length);
205 $start = 0; 214 $start = 0;
206 215
207 $this->meta = array(); 216 $this->meta = array();
208 $this->meta['count'] = 0; 217 $this->meta['count'] = 0;
209 218
210 while ($start < $datalen && 219 while ($start < $datalen
211 (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, 220 && (($pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start)) !== false
212 $start)) != false|| ($pos = $datalen)) 221 || ($pos = $datalen))
213 ) { 222 ) {
214 $len = $pos - $start; 223 $len = $pos - $start;
215 $elem = substr($this->raw_data, $start, $len); 224 $elem = substr($this->raw_data, $start, $len);
216 $start = $pos + 1; 225 $start = $pos + 1;
217 226
272 preg_quote(self::SEPARATOR_ELEMENT, '/'), 281 preg_quote(self::SEPARATOR_ELEMENT, '/'),
273 preg_quote(self::SEPARATOR_ITEM, '/'), 282 preg_quote(self::SEPARATOR_ITEM, '/'),
274 )); 283 ));
275 284
276 if (preg_match("/($begin)$msgid($end)/", $this->raw_data, $m, 285 if (preg_match("/($begin)$msgid($end)/", $this->raw_data, $m,
277 $get_index ? PREG_OFFSET_CAPTURE : null) 286 $get_index ? PREG_OFFSET_CAPTURE : 0)
278 ) { 287 ) {
279 if ($get_index) { 288 if ($get_index) {
280 $idx = 0; 289 $idx = 0;
281 if ($m[0][1]) { 290 if ($m[0][1]) {
282 $idx = substr_count($this->raw_data, self::SEPARATOR_ELEMENT, 0, $m[0][1]+1) 291 $idx = substr_count($this->raw_data, self::SEPARATOR_ELEMENT, 0, $m[0][1]+1)
359 $element = preg_quote(self::SEPARATOR_ELEMENT, '/'); 368 $element = preg_quote(self::SEPARATOR_ELEMENT, '/');
360 $item = preg_quote(self::SEPARATOR_ITEM, '/') . '[0-9]+' . preg_quote(self::SEPARATOR_LEVEL, '/') .'?'; 369 $item = preg_quote(self::SEPARATOR_ITEM, '/') . '[0-9]+' . preg_quote(self::SEPARATOR_LEVEL, '/') .'?';
361 $regexp = '(' . $element . '|' . $item . ')'; 370 $regexp = '(' . $element . '|' . $item . ')';
362 371
363 if (isset($this->meta['pos'][$index])) { 372 if (isset($this->meta['pos'][$index])) {
364 if (preg_match('/([0-9]+)/', $this->raw_data, $m, null, $this->meta['pos'][$index])) 373 if (preg_match('/([0-9]+)/', $this->raw_data, $m, 0, $this->meta['pos'][$index])) {
365 $result = $m[1]; 374 $result = $m[1];
375 }
366 } 376 }
367 else if (isset($this->meta['pos'][$index-1])) { 377 else if (isset($this->meta['pos'][$index-1])) {
368 // get chunk of data after previous element 378 // get chunk of data after previous element
369 $data = substr($this->raw_data, $this->meta['pos'][$index-1]+1, 50); 379 $data = substr($this->raw_data, $this->meta['pos'][$index-1]+1, 50);
370 $data = preg_replace('/^[0-9]+/', '', $data); // remove UID at $index position 380 $data = preg_replace('/^[0-9]+/', '', $data); // remove UID at $index position
389 } 399 }
390 400
391 // Finally use less effective method 401 // Finally use less effective method
392 $data = $this->get(); 402 $data = $this->get();
393 403
394 return $data[$index]; 404 return $data[$index] ?? null;
395 } 405 }
396 406
397 /** 407 /**
398 * Returns response parameters e.g. MAILBOX, ORDER 408 * Returns response parameters e.g. MAILBOX, ORDER
399 * 409 *
419 * 429 *
420 * @param rcube_result_index $index Sorted message identifiers 430 * @param rcube_result_index $index Sorted message identifiers
421 */ 431 */
422 public function sort($index) 432 public function sort($index)
423 { 433 {
424 $this->sort_order = $index->get_parameters('ORDER'); 434 $this->order = $index->get_parameters('ORDER');
425 435
426 if (empty($this->raw_data)) { 436 if (empty($this->raw_data)) {
427 return; 437 return;
428 } 438 }
429 439
438 448
439 // Here we're parsing raw_data twice, we want only one big array 449 // Here we're parsing raw_data twice, we want only one big array
440 // in memory at a time 450 // in memory at a time
441 451
442 // Assign roots 452 // Assign roots
443 while ($start < $datalen && 453 while (
444 (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, 454 ($start < $datalen && ($pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start)))
445 $start)) != false || ($pos = $datalen)) 455 || ($start < $datalen && ($pos = $datalen))
446 ) { 456 ) {
447 $len = $pos - $start; 457 $len = $pos - $start;
448 $elem = substr($this->raw_data, $start, $len); 458 $elem = substr($this->raw_data, $start, $len);
449 $start = $pos + 1; 459 $start = $pos + 1;
450 460
466 476
467 // Re-sort raw data 477 // Re-sort raw data
468 $result = array_fill_keys($result, null); 478 $result = array_fill_keys($result, null);
469 $start = 0; 479 $start = 0;
470 480
471 while ($start < $datalen && 481 while (
472 (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, 482 ($start < $datalen && ($pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start)))
473 $start)) != false || ($pos = $datalen)) 483 || ($start < $datalen && ($pos = $datalen))
474 ) { 484 ) {
475 $len = $pos - $start; 485 $len = $pos - $start;
476 $elem = substr($this->raw_data, $start, $len); 486 $elem = substr($this->raw_data, $start, $len);
477 $start = $pos + 1; 487 $start = $pos + 1;
478 488
494 { 504 {
495 $datalen = strlen($this->raw_data); 505 $datalen = strlen($this->raw_data);
496 $result = array(); 506 $result = array();
497 $start = 0; 507 $start = 0;
498 508
499 while ($start < $datalen && 509 while ($start < $datalen
500 (($pos = @strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start)) != false 510 && (($pos = strpos($this->raw_data, self::SEPARATOR_ELEMENT, $start)) !== false
501 || ($pos = $datalen)) 511 || ($pos = $datalen))
502 ) { 512 ) {
503 $len = $pos - $start; 513 $len = $pos - $start;
504 $elem = substr($this->raw_data, $start, $len); 514 $elem = substr($this->raw_data, $start, $len);
505 $items = explode(self::SEPARATOR_ITEM, $elem); 515 $items = explode(self::SEPARATOR_ITEM, $elem);
506 $result[array_shift($items)] = $this->build_thread($items); 516 $result[array_shift($items)] = $this->build_thread($items);
567 * IMAP THREAD response parser 577 * IMAP THREAD response parser
568 */ 578 */
569 protected function parse_thread($str, $begin = 0, $end = 0, $depth = 0) 579 protected function parse_thread($str, $begin = 0, $end = 0, $depth = 0)
570 { 580 {
571 // Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about 581 // Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about
572 // 7 times instead :-) See comments on http://uk2.php.net/references and this article: 582 // 7 times instead :-) See comments on https://www.php.net/references and this article:
573 // http://derickrethans.nl/files/phparch-php-variables-article.pdf 583 // https://derickrethans.nl/files/phparch-php-variables-article.pdf
574 $node = ''; 584 $node = '';
575 if (!$end) { 585 if (!$end) {
576 $end = strlen($str); 586 $end = strlen($str);
577 } 587 }
578 588
579 // Let's try to store data in max. compacted stracture as a string, 589 // Let's try to store data in max. compacted structure as a string,
580 // arrays handling is much more expensive 590 // arrays handling is much more expensive
581 // For the following structure: THREAD (2)(3 6 (4 23)(44 7 96)) 591 // For the following structure: THREAD (2)(3 6 (4 23)(44 7 96))((11)(12))
582 // -- 2 592 // -- 2
583 // -- 3 593 // -- 3
584 // \-- 6 594 // \-- 6
585 // |-- 4 595 // |-- 4
586 // | \-- 23 596 // | \-- 23
587 // | 597 // |
588 // \-- 44 598 // \-- 44
589 // \-- 7 599 // \-- 7
590 // \-- 96 600 // \-- 96
601 // -- 11
602 // \-- 12
591 // 603 //
592 // The output will be: 2,3^1:6^2:4^3:23^2:44^3:7^4:96 604 // The output will be: 2 3~1:6~2:4~3:23~2:44~3:7~4:96 11~1:12
605 // Note: The "11" thread has no root, we use the first message as root
593 606
594 if ($str[$begin] != '(') { 607 if ($str[$begin] != '(') {
595 // find next bracket 608 // find next bracket
596 $stop = $begin + strcspn($str, '()', $begin, $end - $begin); 609 $stop = $begin + strcspn($str, '()', $begin, $end - $begin);
597 $messages = explode(' ', trim(substr($str, $begin, $stop - $begin))); 610 $messages = explode(' ', trim(substr($str, $begin, $stop - $begin)));
639 $off = $p + 1; 652 $off = $p + 1;
640 $n--; 653 $n--;
641 } 654 }
642 } 655 }
643 656
644 $thread = $this->parse_thread($str, $start + 1, $off - 1, $depth); 657 // Handle threads with missing parent by using first message as root
658 if (substr_compare($str, '((', $start, 2) === 0) {
659 // Extract the current thread, e.g. "((1)(2))"
660 $thread = substr($str, $start, $off - $start);
661 // Length of the first token, e.g. "(1)"
662 $len = strspn($thread, '(0123456789', 1) + 1;
663 // Extract the token and modify it to look like a thread root
664 $token = substr($thread, 1, $len);
665 // Warning: The order is important
666 $token = str_replace('(', '', $token);
667 $token = str_replace(' ', ' (', $token);
668 $token = str_replace(')', ' ', $token);
669 $thread = substr_replace($thread, $token, 1, $len);
670 // Parse the thread
671 $thread = $this->parse_thread($thread, 0, 0, $depth);
672 }
673 else {
674 $thread = $this->parse_thread($str, $start + 1, $off - 1, $depth);
675 }
676
645 if ($thread) { 677 if ($thread) {
646 if (!$depth) { 678 if (!$depth) {
647 if ($node) { 679 if ($node) {
648 $node .= self::SEPARATOR_ELEMENT; 680 $node .= self::SEPARATOR_ELEMENT;
649 } 681 }