Mercurial > hg > rc2
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 } |
