1<?php 2 3namespace dokuwiki\ChangeLog; 4 5/** 6 * methods for handling of changelog of pages or media files 7 */ 8abstract class ChangeLog 9{ 10 /** @var string */ 11 protected $id; 12 /** @var int */ 13 protected $chunk_size; 14 /** @var array */ 15 protected $flags; 16 /** @var array */ 17 protected $cache; 18 19 /** 20 * Constructor 21 * 22 * @param string $id page id 23 * @param int $chunk_size maximum block size read from file 24 */ 25 public function __construct($id, $chunk_size = 8192) 26 { 27 global $cache_revinfo; 28 29 $this->cache =& $cache_revinfo; 30 if (!isset($this->cache[$id])) { 31 $this->cache[$id] = array(); 32 } 33 34 $this->id = $id; 35 $this->setChunkSize($chunk_size); 36 37 $this->flags['ignore_external_edit'] = false; 38 // FIXME: see unittest class changelog_getrevisionsaround_test 39 // test page "mailinglist.txt" is newer than last entry of meta/mailinglist.changes 40 // temporary disable external edit check to prevent failures during unittest 41 if (defined('DOKU_UNITTEST')) { 42 $this->setFlags('ignore_external_edit', true); 43 } 44 } 45 46 /** 47 * Set chunk size for file reading 48 * Chunk size zero let read whole file at once 49 * 50 * @param int $chunk_size maximum block size read from file 51 */ 52 public function setChunkSize($chunk_size) 53 { 54 if (!is_numeric($chunk_size)) $chunk_size = 0; 55 56 $this->chunk_size = (int)max($chunk_size, 0); 57 } 58 59 /** 60 * Set flags for ChangeLog 61 * 62 * @param string $name 63 * @param mixed $value 64 * @return bool 65 */ 66 public function setFlags($name, $value) 67 { 68 if (array_key_exists($name, $this->flags)) { 69 $this->flags[$name] = $value; 70 return true; 71 } 72 return false; 73 } 74 75 /** 76 * Returns path to changelog 77 * 78 * @return string path to file 79 */ 80 abstract protected function getChangelogFilename(); 81 82 /** 83 * Returns path to current page/media 84 * 85 * @return string path to file 86 */ 87 abstract protected function getFilename(); 88 89 /** 90 * Get the changelog information for a specific page id and revision (timestamp) 91 * 92 * Adjacent changelog lines are optimistically parsed and cached to speed up 93 * consecutive calls to getRevisionInfo. For large changelog files, only the chunk 94 * containing the requested changelog line is read. 95 * 96 * @param int $rev revision timestamp 97 * @return bool|array false or array with entries: 98 * - date: unix timestamp 99 * - ip: IPv4 address (127.0.0.1) 100 * - type: log line type 101 * - id: page id 102 * - user: user name 103 * - sum: edit summary (or action reason) 104 * - extra: extra data (varies by line type) 105 * - sizechange: change of filesize 106 * 107 * @author Ben Coburn <btcoburn@silicodon.net> 108 * @author Kate Arzamastseva <pshns@ukr.net> 109 */ 110 public function getRevisionInfo($rev) 111 { 112 $rev = max(0, $rev); 113 if (!$rev) return false; 114 115 // check if it's already in the memory cache 116 if (isset($this->cache[$this->id]) && isset($this->cache[$this->id][$rev])) { 117 return $this->cache[$this->id][$rev]; 118 } 119 120 //read lines from changelog 121 list($fp, $lines) = $this->readloglines($rev); 122 if ($fp) { 123 fclose($fp); 124 } 125 if (empty($lines)) return false; 126 127 // parse and cache changelog lines 128 foreach ($lines as $value) { 129 $tmp = parseChangelogLine($value); 130 if ($tmp !== false) { 131 $this->cache[$this->id][$tmp['date']] = $tmp; 132 } 133 } 134 if (!isset($this->cache[$this->id][$rev])) { 135 return false; 136 } 137 return $this->cache[$this->id][$rev]; 138 } 139 140 /** 141 * Return a list of page revisions numbers 142 * 143 * Does not guarantee that the revision exists in the attic, 144 * only that a line with the date exists in the changelog. 145 * By default the current revision is skipped. 146 * 147 * The current revision is automatically skipped when the page exists. 148 * See $INFO['meta']['last_change'] for the current revision. 149 * A negative $first let read the current revision too. 150 * 151 * For efficiency, the log lines are parsed and cached for later 152 * calls to getRevisionInfo. Large changelog files are read 153 * backwards in chunks until the requested number of changelog 154 * lines are recieved. 155 * 156 * @param int $first skip the first n changelog lines 157 * @param int $num number of revisions to return 158 * @return array with the revision timestamps 159 * 160 * @author Ben Coburn <btcoburn@silicodon.net> 161 * @author Kate Arzamastseva <pshns@ukr.net> 162 */ 163 public function getRevisions($first, $num) 164 { 165 $revs = array(); 166 $lines = array(); 167 $count = 0; 168 169 $num = max($num, 0); 170 if ($num == 0) { 171 return $revs; 172 } 173 174 if ($first < 0) { 175 $first = 0; 176 } else { 177 if (file_exists($this->getFilename())) { 178 // skip current revision if the page exists 179 $first = max($first + 1, 0); 180 } 181 } 182 183 $file = $this->getChangelogFilename(); 184 185 if (!file_exists($file)) { 186 return $revs; 187 } 188 if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) { 189 // read whole file 190 $lines = file($file); 191 if ($lines === false) { 192 return $revs; 193 } 194 } else { 195 // read chunks backwards 196 $fp = fopen($file, 'rb'); // "file pointer" 197 if ($fp === false) { 198 return $revs; 199 } 200 fseek($fp, 0, SEEK_END); 201 $tail = ftell($fp); 202 203 // chunk backwards 204 $finger = max($tail - $this->chunk_size, 0); 205 while ($count < $num + $first) { 206 $nl = $this->getNewlinepointer($fp, $finger); 207 208 // was the chunk big enough? if not, take another bite 209 if ($nl > 0 && $tail <= $nl) { 210 $finger = max($finger - $this->chunk_size, 0); 211 continue; 212 } else { 213 $finger = $nl; 214 } 215 216 // read chunk 217 $chunk = ''; 218 $read_size = max($tail - $finger, 0); // found chunk size 219 $got = 0; 220 while ($got < $read_size && !feof($fp)) { 221 $tmp = @fread($fp, max(min($this->chunk_size, $read_size - $got), 0)); 222 if ($tmp === false) { 223 break; 224 } //error state 225 $got += strlen($tmp); 226 $chunk .= $tmp; 227 } 228 $tmp = explode("\n", $chunk); 229 array_pop($tmp); // remove trailing newline 230 231 // combine with previous chunk 232 $count += count($tmp); 233 $lines = array_merge($tmp, $lines); 234 235 // next chunk 236 if ($finger == 0) { 237 break; 238 } else { // already read all the lines 239 $tail = $finger; 240 $finger = max($tail - $this->chunk_size, 0); 241 } 242 } 243 fclose($fp); 244 } 245 246 // skip parsing extra lines 247 $num = max(min(count($lines) - $first, $num), 0); 248 if ($first > 0 && $num > 0) { 249 $lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num); 250 } else { 251 if ($first > 0 && $num == 0) { 252 $lines = array_slice($lines, 0, max(count($lines) - $first, 0)); 253 } elseif ($first == 0 && $num > 0) { 254 $lines = array_slice($lines, max(count($lines) - $num, 0)); 255 } 256 } 257 258 // handle lines in reverse order 259 for ($i = count($lines) - 1; $i >= 0; $i--) { 260 $tmp = parseChangelogLine($lines[$i]); 261 if ($tmp !== false) { 262 $this->cache[$this->id][$tmp['date']] = $tmp; 263 $revs[] = $tmp['date']; 264 } 265 } 266 267 return $revs; 268 } 269 270 /** 271 * Get the nth revision left or right handside for a specific page id and revision (timestamp) 272 * 273 * For large changelog files, only the chunk containing the 274 * reference revision $rev is read and sometimes a next chunck. 275 * 276 * Adjacent changelog lines are optimistically parsed and cached to speed up 277 * consecutive calls to getRevisionInfo. 278 * 279 * @param int $rev revision timestamp used as startdate (doesn't need to be revisionnumber) 280 * @param int $direction give position of returned revision with respect to $rev; positive=next, negative=prev 281 * @return bool|int 282 * timestamp of the requested revision 283 * otherwise false 284 */ 285 public function getRelativeRevision($rev, $direction) 286 { 287 $rev = max($rev, 0); 288 $direction = (int)$direction; 289 290 //no direction given or last rev, so no follow-up 291 if (!$direction || ($direction > 0 && $this->isCurrentRevision($rev))) { 292 return false; 293 } 294 295 //get lines from changelog 296 list($fp, $lines, $head, $tail, $eof) = $this->readloglines($rev); 297 if (empty($lines)) return false; 298 299 // look for revisions later/earlier than $rev, when founded count till the wanted revision is reached 300 // also parse and cache changelog lines for getRevisionInfo(). 301 $revcounter = 0; 302 $relativerev = false; 303 $checkotherchunck = true; //always runs once 304 while (!$relativerev && $checkotherchunck) { 305 $tmp = array(); 306 //parse in normal or reverse order 307 $count = count($lines); 308 if ($direction > 0) { 309 $start = 0; 310 $step = 1; 311 } else { 312 $start = $count - 1; 313 $step = -1; 314 } 315 for ($i = $start; $i >= 0 && $i < $count; $i = $i + $step) { 316 $tmp = parseChangelogLine($lines[$i]); 317 if ($tmp !== false) { 318 $this->cache[$this->id][$tmp['date']] = $tmp; 319 //look for revs older/earlier then reference $rev and select $direction-th one 320 if (($direction > 0 && $tmp['date'] > $rev) || ($direction < 0 && $tmp['date'] < $rev)) { 321 $revcounter++; 322 if ($revcounter == abs($direction)) { 323 $relativerev = $tmp['date']; 324 } 325 } 326 } 327 } 328 329 //true when $rev is found, but not the wanted follow-up. 330 $checkotherchunck = $fp 331 && ($tmp['date'] == $rev || ($revcounter > 0 && !$relativerev)) 332 && !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0)); 333 334 if ($checkotherchunck) { 335 list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, $direction); 336 337 if (empty($lines)) break; 338 } 339 } 340 if ($fp) { 341 fclose($fp); 342 } 343 344 return $relativerev; 345 } 346 347 /** 348 * Returns revisions around rev1 and rev2 349 * When available it returns $max entries for each revision 350 * 351 * @param int $rev1 oldest revision timestamp 352 * @param int $rev2 newest revision timestamp (0 looks up last revision) 353 * @param int $max maximum number of revisions returned 354 * @return array with two arrays with revisions surrounding rev1 respectively rev2 355 */ 356 public function getRevisionsAround($rev1, $rev2, $max = 50) 357 { 358 $max = intval(abs($max) / 2) * 2 + 1; 359 $rev1 = max($rev1, 0); 360 $rev2 = max($rev2, 0); 361 362 if ($rev2) { 363 if ($rev2 < $rev1) { 364 $rev = $rev2; 365 $rev2 = $rev1; 366 $rev1 = $rev; 367 } 368 } else { 369 //empty right side means a removed page. Look up last revision. 370 $revs = $this->getRevisions(-1, 1); 371 $rev2 = $revs[0]; 372 } 373 //collect revisions around rev2 374 list($revs2, $allrevs, $fp, $lines, $head, $tail) = $this->retrieveRevisionsAround($rev2, $max); 375 376 if (empty($revs2)) return array(array(), array()); 377 378 //collect revisions around rev1 379 $index = array_search($rev1, $allrevs); 380 if ($index === false) { 381 //no overlapping revisions 382 list($revs1, , , , ,) = $this->retrieveRevisionsAround($rev1, $max); 383 if (empty($revs1)) $revs1 = array(); 384 } else { 385 //revisions overlaps, reuse revisions around rev2 386 $lastrev = array_pop($allrevs); //keep last entry that could be external edit 387 $revs1 = $allrevs; 388 while ($head > 0) { 389 for ($i = count($lines) - 1; $i >= 0; $i--) { 390 $tmp = parseChangelogLine($lines[$i]); 391 if ($tmp !== false) { 392 $this->cache[$this->id][$tmp['date']] = $tmp; 393 $revs1[] = $tmp['date']; 394 $index++; 395 396 if ($index > intval($max / 2)) break 2; 397 } 398 } 399 400 list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1); 401 } 402 sort($revs1); 403 $revs1[] = $lastrev; //push back last entry 404 405 //return wanted selection 406 $revs1 = array_slice($revs1, max($index - intval($max / 2), 0), $max); 407 } 408 409 return array(array_reverse($revs1), array_reverse($revs2)); 410 } 411 412 413 /** 414 * Checks if the ID has old revisons 415 * @return boolean 416 */ 417 public function hasRevisions() { 418 $file = $this->getChangelogFilename(); 419 return file_exists($file); 420 } 421 422 /** 423 * Returns lines from changelog. 424 * If file larger than $chuncksize, only chunck is read that could contain $rev. 425 * 426 * @param int $rev revision timestamp 427 * @return array|false 428 * if success returns array(fp, array(changeloglines), $head, $tail, $eof) 429 * where fp only defined for chuck reading, needs closing. 430 * otherwise false 431 */ 432 protected function readloglines($rev) 433 { 434 $file = $this->getChangelogFilename(); 435 436 if (!file_exists($file)) { 437 return false; 438 } 439 440 $fp = null; 441 $head = 0; 442 $tail = 0; 443 $eof = 0; 444 445 if (filesize($file) < $this->chunk_size || $this->chunk_size == 0) { 446 // read whole file 447 $lines = file($file); 448 if ($lines === false) { 449 return false; 450 } 451 } else { 452 // read by chunk 453 $fp = fopen($file, 'rb'); // "file pointer" 454 if ($fp === false) { 455 return false; 456 } 457 $head = 0; 458 fseek($fp, 0, SEEK_END); 459 $eof = ftell($fp); 460 $tail = $eof; 461 462 // find chunk 463 while ($tail - $head > $this->chunk_size) { 464 $finger = $head + intval(($tail - $head) / 2); 465 $finger = $this->getNewlinepointer($fp, $finger); 466 $tmp = fgets($fp); 467 if ($finger == $head || $finger == $tail) { 468 break; 469 } 470 $tmp = parseChangelogLine($tmp); 471 $finger_rev = $tmp['date']; 472 473 if ($finger_rev > $rev) { 474 $tail = $finger; 475 } else { 476 $head = $finger; 477 } 478 } 479 480 if ($tail - $head < 1) { 481 // cound not find chunk, assume requested rev is missing 482 fclose($fp); 483 return false; 484 } 485 486 $lines = $this->readChunk($fp, $head, $tail); 487 } 488 return array( 489 $fp, 490 $lines, 491 $head, 492 $tail, 493 $eof, 494 ); 495 } 496 497 /** 498 * Read chunk and return array with lines of given chunck. 499 * Has no check if $head and $tail are really at a new line 500 * 501 * @param resource $fp resource filepointer 502 * @param int $head start point chunck 503 * @param int $tail end point chunck 504 * @return array lines read from chunck 505 */ 506 protected function readChunk($fp, $head, $tail) 507 { 508 $chunk = ''; 509 $chunk_size = max($tail - $head, 0); // found chunk size 510 $got = 0; 511 fseek($fp, $head); 512 while ($got < $chunk_size && !feof($fp)) { 513 $tmp = @fread($fp, max(min($this->chunk_size, $chunk_size - $got), 0)); 514 if ($tmp === false) { //error state 515 break; 516 } 517 $got += strlen($tmp); 518 $chunk .= $tmp; 519 } 520 $lines = explode("\n", $chunk); 521 array_pop($lines); // remove trailing newline 522 return $lines; 523 } 524 525 /** 526 * Set pointer to first new line after $finger and return its position 527 * 528 * @param resource $fp filepointer 529 * @param int $finger a pointer 530 * @return int pointer 531 */ 532 protected function getNewlinepointer($fp, $finger) 533 { 534 fseek($fp, $finger); 535 $nl = $finger; 536 if ($finger > 0) { 537 fgets($fp); // slip the finger forward to a new line 538 $nl = ftell($fp); 539 } 540 return $nl; 541 } 542 543 /** 544 * Check whether given revision is the current page 545 * 546 * @param int $rev timestamp of current page 547 * @return bool true if $rev is current revision, otherwise false 548 */ 549 public function isCurrentRevision($rev) 550 { 551 return $rev == @filemtime($this->getFilename()); 552 } 553 554 /** 555 * Return an existing revision for a specific date which is 556 * the current one or younger or equal then the date 557 * 558 * @param number $date_at timestamp 559 * @return string revision ('' for current) 560 */ 561 public function getLastRevisionAt($date_at) 562 { 563 //requested date_at(timestamp) younger or equal then modified_time($this->id) => load current 564 if (file_exists($this->getFilename()) && $date_at >= @filemtime($this->getFilename())) { 565 return ''; 566 } else { 567 if ($rev = $this->getRelativeRevision($date_at + 1, -1)) { //+1 to get also the requested date revision 568 return $rev; 569 } else { 570 return false; 571 } 572 } 573 } 574 575 /** 576 * Returns the next lines of the changelog of the chunck before head or after tail 577 * 578 * @param resource $fp filepointer 579 * @param int $head position head of last chunk 580 * @param int $tail position tail of last chunk 581 * @param int $direction positive forward, negative backward 582 * @return array with entries: 583 * - $lines: changelog lines of readed chunk 584 * - $head: head of chunk 585 * - $tail: tail of chunk 586 */ 587 protected function readAdjacentChunk($fp, $head, $tail, $direction) 588 { 589 if (!$fp) return array(array(), $head, $tail); 590 591 if ($direction > 0) { 592 //read forward 593 $head = $tail; 594 $tail = $head + intval($this->chunk_size * (2 / 3)); 595 $tail = $this->getNewlinepointer($fp, $tail); 596 } else { 597 //read backward 598 $tail = $head; 599 $head = max($tail - $this->chunk_size, 0); 600 while (true) { 601 $nl = $this->getNewlinepointer($fp, $head); 602 // was the chunk big enough? if not, take another bite 603 if ($nl > 0 && $tail <= $nl) { 604 $head = max($head - $this->chunk_size, 0); 605 } else { 606 $head = $nl; 607 break; 608 } 609 } 610 } 611 612 //load next chunck 613 $lines = $this->readChunk($fp, $head, $tail); 614 return array($lines, $head, $tail); 615 } 616 617 /** 618 * Collect the $max revisions near to the timestamp $rev 619 * 620 * @param int $rev revision timestamp 621 * @param int $max maximum number of revisions to be returned 622 * @return bool|array 623 * return array with entries: 624 * - $requestedrevs: array of with $max revision timestamps 625 * - $revs: all parsed revision timestamps 626 * - $fp: filepointer only defined for chuck reading, needs closing. 627 * - $lines: non-parsed changelog lines before the parsed revisions 628 * - $head: position of first readed changelogline 629 * - $lasttail: position of end of last readed changelogline 630 * otherwise false 631 */ 632 protected function retrieveRevisionsAround($rev, $max) 633 { 634 $lastChangelogRev = 0; 635 $externaleditRevinfo = $this->getExternalEditRevInfo(); 636 637 if ($externaleditRevinfo) { 638 $revisions = $this->getRevisions(-1, 1); 639 $lastChangelogRev = $revisions[0]; 640 if ($externaleditRevinfo['date'] == $rev) { 641 $rev = $lastChangelogRev; //replace by an existing changelog line 642 } 643 } 644 645 $revs = array(); 646 $aftercount = $beforecount = 0; 647 648 //get lines from changelog 649 list($fp, $lines, $starthead, $starttail, /* $eof */) = $this->readloglines($rev); 650 if (empty($lines)) { 651 if ($externaleditRevinfo) { 652 $revs[] = $externaleditRevinfo['date']; 653 return array($revs, $revs, false, [], 0, 0); 654 } else { 655 return false; 656 } 657 } 658 659 //parse chunk containing $rev, and read forward more chunks until $max/2 is reached 660 $head = $starthead; 661 $tail = $starttail; 662 while (count($lines) > 0) { 663 foreach ($lines as $line) { 664 $tmp = parseChangelogLine($line); 665 if ($tmp !== false) { 666 $this->cache[$this->id][$tmp['date']] = $tmp; 667 $revs[] = $tmp['date']; 668 if ($tmp['date'] >= $rev) { 669 //count revs after reference $rev 670 $aftercount++; 671 if ($aftercount == 1) $beforecount = count($revs); 672 } 673 //enough revs after reference $rev? 674 if ($aftercount > intval($max / 2)) break 2; 675 } 676 } 677 //retrieve next chunk 678 list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1); 679 } 680 if ($aftercount == 0) return false; 681 682 $lasttail = $tail; 683 684 //read additional chuncks backward until $max/2 is reached and total number of revs is equal to $max 685 $lines = array(); 686 $i = 0; 687 if ($aftercount > 0) { 688 $head = $starthead; 689 $tail = $starttail; 690 while ($head > 0) { 691 list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1); 692 693 for ($i = count($lines) - 1; $i >= 0; $i--) { 694 $tmp = parseChangelogLine($lines[$i]); 695 if ($tmp !== false) { 696 $this->cache[$this->id][$tmp['date']] = $tmp; 697 $revs[] = $tmp['date']; 698 $beforecount++; 699 //enough revs before reference $rev? 700 if ($beforecount > max(intval($max / 2), $max - $aftercount)) break 2; 701 } 702 } 703 } 704 } 705 sort($revs); 706 707 //add external edit when the last value is identical with the last revision in the changelog 708 if ($externaleditRevinfo && $revs[count($revs)-1] == $lastChangelogRev) { 709 $revs[] = $externaleditRevinfo['date']; 710 } 711 712 //keep only non-parsed lines 713 $lines = array_slice($lines, 0, $i); 714 //trunk desired selection 715 $requestedrevs = array_slice($revs, -$max, $max); 716 717 return array($requestedrevs, $revs, $fp, $lines, $head, $lasttail); 718 } 719 720 /** 721 * Returns revision logline in same format as @see ChangeLog::getRevisionInfo() 722 * 723 * @return bool|array false if not external edit/deletion, otherwise array with entries: 724 * - date: unix timestamp for external edit or 'unknown' for external deletion 725 * - ip: IPv4 address (127.0.0.1) 726 * - type: log line type 727 * - id: page id 728 * - user: user name 729 * - sum: edit summary (or action reason) 730 * - extra: extra data (varies by line type) 731 * - sizechange: change of filesize 732 * 733 * @author Gerrit Uitslag <klapinklapin@gmail.com> 734 */ 735 public function getExternalEditRevInfo() 736 { 737 global $lang; 738 global $cache_externaledit; //caches external edits per page 739 740 if ($this->flags['ignore_external_edit']) return false; 741 742 // check if it's already in the memory cache 743 if (isset($cache_externaledit[$this->id])) { 744 if ($cache_externaledit[$this->id] === false) { 745 return false; 746 } else { 747 return $this->cache[$this->id][$cache_externaledit[$this->id]]; 748 } 749 } 750 $externaleditRevinfo = false; 751 $cache_externaledit[$this->id] = false; 752 753 //in attic no revision of current existing wiki page, so external edit occurred 754 $fileLastMod = $this->getFilename(); 755 $lastMod = @filemtime($fileLastMod); // from wiki page, suppresses warning in case the file not exists 756 $lastRev = $this->getRevisions(-1, 1); // from changelog 757 $lastRev = (int) (empty($lastRev) ? 0 : $lastRev[0]); 758 if (!file_exists($this->getFilename($lastMod)) && file_exists($fileLastMod) && $lastRev < $lastMod) { 759 $cache_externaledit[$this->id] = $lastMod; 760 $fileLastRev = $this->getFilename($lastRev); //returns current wikipage path if $lastRev==false 761 $revinfo = $this->getRevisionInfo($lastRev); 762 if (empty($lastRev) || !file_exists($fileLastRev) || $revinfo['type'] == DOKU_CHANGE_TYPE_DELETE) { 763 $filesize_old = 0; 764 } else { 765 $filesize_old = io_getSizeFile($fileLastRev); 766 } 767 $filesize_new = filesize($fileLastMod); 768 $sizechange = $filesize_new - $filesize_old; 769 $isJustCreated = empty($lastRev) || !file_exists($fileLastRev); 770 771 $externaleditRevinfo = [ 772 'date' => $lastMod, 773 'ip' => '127.0.0.1', 774 'type' => $isJustCreated ? DOKU_CHANGE_TYPE_CREATE : DOKU_CHANGE_TYPE_EDIT, 775 'id' => $this->id, 776 'user' => '', 777 'sum' => ($isJustCreated ? $lang['created'] .' - ' : '') . $lang['external_edit'], 778 'extra' => '', 779 'sizechange' => $sizechange 780 ]; 781 $cache_externaledit[$this->id] = $externaleditRevinfo['date']; 782 $this->cache[$this->id][$externaleditRevinfo['date']] = $externaleditRevinfo; 783 } 784 785 $revinfo = $this->getRevisionInfo($lastRev); 786 //deleted wiki page, but not registered in changelog 787 if (!file_exists($fileLastMod) // there is no current page=>true 788 && !empty($lastRev) && $revinfo['type'] !== DOKU_CHANGE_TYPE_DELETE 789 ) { 790 $fileLastRev = $this->getFilename($lastRev); 791 $externaleditRevinfo = [ 792 'date' => time(), //unknown deletion date, always higher as latest rev 793 'ip' => '127.0.0.1', 794 'type' => DOKU_CHANGE_TYPE_EXTERNAL_DELETE, 795 'id' => $this->id, 796 'user' => '', 797 'sum' => $lang['deleted']. ' - ' . $lang['external_edit'], 798 'extra' => '', 799 'sizechange' => -io_getSizeFile($fileLastRev) 800 ]; 801 $cache_externaledit[$this->id] = $externaleditRevinfo['date']; 802 $this->cache[$this->id][$externaleditRevinfo['date']] = $externaleditRevinfo; 803 } 804 805 return $externaleditRevinfo; 806 } 807} 808