[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ChangeLog/ -> ChangeLog.php (source)

   1  <?php
   2  
   3  namespace dokuwiki\ChangeLog;
   4  
   5  use dokuwiki\Logger;
   6  
   7  /**
   8   * ChangeLog Prototype; methods for handling changelog
   9   */
  10  abstract class ChangeLog
  11  {
  12      use ChangeLogTrait;
  13  
  14      /** @var string */
  15      protected $id;
  16      /** @var false|int */
  17      protected $currentRevision;
  18      /** @var array */
  19      protected $cache;
  20  
  21      /**
  22       * Constructor
  23       *
  24       * @param string $id page id
  25       * @param int $chunk_size maximum block size read from file
  26       */
  27      public function __construct($id, $chunk_size = 8192)
  28      {
  29          global $cache_revinfo;
  30  
  31          $this->cache =& $cache_revinfo;
  32          if (!isset($this->cache[$id])) {
  33              $this->cache[$id] = array();
  34          }
  35  
  36          $this->id = $id;
  37          $this->setChunkSize($chunk_size);
  38      }
  39  
  40      /**
  41       * Returns path to current page/media
  42       *
  43       * @return string path to file
  44       */
  45      abstract protected function getFilename();
  46  
  47      /**
  48       * Check whether given revision is the current page
  49       *
  50       * @param int $rev timestamp of current page
  51       * @return bool true if $rev is current revision, otherwise false
  52       */
  53      public function isCurrentRevision($rev)
  54      {
  55          return $rev == $this->currentRevision();
  56      }
  57  
  58      /**
  59       * Checks if the revision is last revision
  60       *
  61       * @param int $rev revision timestamp
  62       * @return bool true if $rev is last revision, otherwise false
  63       */
  64      public function isLastRevision($rev = null)
  65      {
  66          return $rev === $this->lastRevision();
  67      }
  68  
  69      /**
  70       * Return the current revision identifier
  71       *
  72       * The "current" revision means current version of the page or media file. It is either
  73       * identical with or newer than the "last" revision, that depends on whether the file
  74       * has modified, created or deleted outside of DokuWiki.
  75       * The value of identifier can be determined by timestamp as far as the file exists,
  76       * otherwise it must be assigned larger than any other revisions to keep them sortable.
  77       *
  78       * @return int|false revision timestamp
  79       */
  80      public function currentRevision()
  81      {
  82          if (!isset($this->currentRevision)) {
  83              // set ChangeLog::currentRevision property
  84              $this->getCurrentRevisionInfo();
  85          }
  86          return $this->currentRevision;
  87      }
  88  
  89      /**
  90       * Return the last revision identifier, date value of the last entry of the changelog
  91       *
  92       * @return int|false revision timestamp
  93       */
  94      public function lastRevision()
  95      {
  96          $revs = $this->getRevisions(-1, 1);
  97          return empty($revs) ? false : $revs[0];
  98      }
  99  
 100      /**
 101       * Save revision info to the cache pool
 102       *
 103       * @param array $info Revision info structure
 104       * @return bool
 105       */
 106      protected function cacheRevisionInfo($info)
 107      {
 108          if (!is_array($info)) return false;
 109          //$this->cache[$this->id][$info['date']] ??= $info; // since php 7.4
 110          $this->cache[$this->id][$info['date']] = $this->cache[$this->id][$info['date']] ?? $info;
 111          return true;
 112      }
 113  
 114      /**
 115       * Get the changelog information for a specific revision (timestamp)
 116       *
 117       * Adjacent changelog lines are optimistically parsed and cached to speed up
 118       * consecutive calls to getRevisionInfo. For large changelog files, only the chunk
 119       * containing the requested changelog line is read.
 120       *
 121       * @param int $rev revision timestamp
 122       * @param bool $retrieveCurrentRevInfo allows to skip for getting other revision info in the
 123       *                                     getCurrentRevisionInfo() where $currentRevision is not yet determined
 124       * @return bool|array false or array with entries:
 125       *      - date:  unix timestamp
 126       *      - ip:    IPv4 address (127.0.0.1)
 127       *      - type:  log line type
 128       *      - id:    page id
 129       *      - user:  user name
 130       *      - sum:   edit summary (or action reason)
 131       *      - extra: extra data (varies by line type)
 132       *      - sizechange: change of filesize
 133       *
 134       * @author Ben Coburn <btcoburn@silicodon.net>
 135       * @author Kate Arzamastseva <pshns@ukr.net>
 136       */
 137      public function getRevisionInfo($rev, $retrieveCurrentRevInfo = true)
 138      {
 139          $rev = max(0, $rev);
 140          if (!$rev) return false;
 141  
 142          //ensure the external edits are cached as well
 143          if (!isset($this->currentRevision) && $retrieveCurrentRevInfo) {
 144              $this->getCurrentRevisionInfo();
 145          }
 146  
 147          // check if it's already in the memory cache
 148          if (isset($this->cache[$this->id]) && isset($this->cache[$this->id][$rev])) {
 149              return $this->cache[$this->id][$rev];
 150          }
 151  
 152          //read lines from changelog
 153          list($fp, $lines) = $this->readloglines($rev);
 154          if ($fp) {
 155              fclose($fp);
 156          }
 157          if (empty($lines)) return false;
 158  
 159          // parse and cache changelog lines
 160          foreach ($lines as $value) {
 161              $info = $this->parseLogLine($value);
 162              $this->cacheRevisionInfo($info);
 163          }
 164          if (!isset($this->cache[$this->id][$rev])) {
 165              return false;
 166          }
 167          return $this->cache[$this->id][$rev];
 168      }
 169  
 170      /**
 171       * Return a list of page revisions numbers
 172       *
 173       * Does not guarantee that the revision exists in the attic,
 174       * only that a line with the date exists in the changelog.
 175       * By default the current revision is skipped.
 176       *
 177       * The current revision is automatically skipped when the page exists.
 178       * See $INFO['meta']['last_change'] for the current revision.
 179       * A negative $first let read the current revision too.
 180       *
 181       * For efficiency, the log lines are parsed and cached for later
 182       * calls to getRevisionInfo. Large changelog files are read
 183       * backwards in chunks until the requested number of changelog
 184       * lines are received.
 185       *
 186       * @param int $first skip the first n changelog lines
 187       * @param int $num number of revisions to return
 188       * @return array with the revision timestamps
 189       *
 190       * @author Ben Coburn <btcoburn@silicodon.net>
 191       * @author Kate Arzamastseva <pshns@ukr.net>
 192       */
 193      public function getRevisions($first, $num)
 194      {
 195          $revs = array();
 196          $lines = array();
 197          $count = 0;
 198  
 199          $logfile = $this->getChangelogFilename();
 200          if (!file_exists($logfile)) return $revs;
 201  
 202          $num = max($num, 0);
 203          if ($num == 0) {
 204              return $revs;
 205          }
 206  
 207          if ($first < 0) {
 208              $first = 0;
 209          } else {
 210              $fileLastMod = $this->getFilename();
 211              if (file_exists($fileLastMod) && $this->isLastRevision(filemtime($fileLastMod))) {
 212                  // skip last revision if the page exists
 213                  $first = max($first + 1, 0);
 214              }
 215          }
 216  
 217          if (filesize($logfile) < $this->chunk_size || $this->chunk_size == 0) {
 218              // read whole file
 219              $lines = file($logfile);
 220              if ($lines === false) {
 221                  return $revs;
 222              }
 223          } else {
 224              // read chunks backwards
 225              $fp = fopen($logfile, 'rb'); // "file pointer"
 226              if ($fp === false) {
 227                  return $revs;
 228              }
 229              fseek($fp, 0, SEEK_END);
 230              $tail = ftell($fp);
 231  
 232              // chunk backwards
 233              $finger = max($tail - $this->chunk_size, 0);
 234              while ($count < $num + $first) {
 235                  $nl = $this->getNewlinepointer($fp, $finger);
 236  
 237                  // was the chunk big enough? if not, take another bite
 238                  if ($nl > 0 && $tail <= $nl) {
 239                      $finger = max($finger - $this->chunk_size, 0);
 240                      continue;
 241                  } else {
 242                      $finger = $nl;
 243                  }
 244  
 245                  // read chunk
 246                  $chunk = '';
 247                  $read_size = max($tail - $finger, 0); // found chunk size
 248                  $got = 0;
 249                  while ($got < $read_size && !feof($fp)) {
 250                      $tmp = @fread($fp, max(min($this->chunk_size, $read_size - $got), 0));
 251                      if ($tmp === false) {
 252                          break;
 253                      } //error state
 254                      $got += strlen($tmp);
 255                      $chunk .= $tmp;
 256                  }
 257                  $tmp = explode("\n", $chunk);
 258                  array_pop($tmp); // remove trailing newline
 259  
 260                  // combine with previous chunk
 261                  $count += count($tmp);
 262                  $lines = array_merge($tmp, $lines);
 263  
 264                  // next chunk
 265                  if ($finger == 0) {
 266                      break;
 267                  } else { // already read all the lines
 268                      $tail = $finger;
 269                      $finger = max($tail - $this->chunk_size, 0);
 270                  }
 271              }
 272              fclose($fp);
 273          }
 274  
 275          // skip parsing extra lines
 276          $num = max(min(count($lines) - $first, $num), 0);
 277          if ($first > 0 && $num > 0) {
 278              $lines = array_slice($lines, max(count($lines) - $first - $num, 0), $num);
 279          } elseif ($first > 0 && $num == 0) {
 280              $lines = array_slice($lines, 0, max(count($lines) - $first, 0));
 281          } elseif ($first == 0 && $num > 0) {
 282              $lines = array_slice($lines, max(count($lines) - $num, 0));
 283          }
 284  
 285          // handle lines in reverse order
 286          for ($i = count($lines) - 1; $i >= 0; $i--) {
 287              $info = $this->parseLogLine($lines[$i]);
 288              if ($this->cacheRevisionInfo($info)) {
 289                  $revs[] = $info['date'];
 290              }
 291          }
 292  
 293          return $revs;
 294      }
 295  
 296      /**
 297       * Get the nth revision left or right-hand side  for a specific page id and revision (timestamp)
 298       *
 299       * For large changelog files, only the chunk containing the
 300       * reference revision $rev is read and sometimes a next chunk.
 301       *
 302       * Adjacent changelog lines are optimistically parsed and cached to speed up
 303       * consecutive calls to getRevisionInfo.
 304       *
 305       * @param int $rev revision timestamp used as start date
 306       *    (doesn't need to be exact revision number)
 307       * @param int $direction give position of returned revision with respect to $rev;
 308            positive=next, negative=prev
 309       * @return bool|int
 310       *      timestamp of the requested revision
 311       *      otherwise false
 312       */
 313      public function getRelativeRevision($rev, $direction)
 314      {
 315          $rev = max($rev, 0);
 316          $direction = (int)$direction;
 317  
 318          //no direction given or last rev, so no follow-up
 319          if (!$direction || ($direction > 0 && $this->isCurrentRevision($rev))) {
 320              return false;
 321          }
 322  
 323          //get lines from changelog
 324          list($fp, $lines, $head, $tail, $eof) = $this->readloglines($rev);
 325          if (empty($lines)) return false;
 326  
 327          // look for revisions later/earlier than $rev, when founded count till the wanted revision is reached
 328          // also parse and cache changelog lines for getRevisionInfo().
 329          $revCounter = 0;
 330          $relativeRev = false;
 331          $checkOtherChunk = true; //always runs once
 332          while (!$relativeRev && $checkOtherChunk) {
 333              $info = array();
 334              //parse in normal or reverse order
 335              $count = count($lines);
 336              if ($direction > 0) {
 337                  $start = 0;
 338                  $step = 1;
 339              } else {
 340                  $start = $count - 1;
 341                  $step = -1;
 342              }
 343              for ($i = $start; $i >= 0 && $i < $count; $i = $i + $step) {
 344                  $info = $this->parseLogLine($lines[$i]);
 345                  if ($this->cacheRevisionInfo($info)) {
 346                      //look for revs older/earlier then reference $rev and select $direction-th one
 347                      if (($direction > 0 && $info['date'] > $rev) || ($direction < 0 && $info['date'] < $rev)) {
 348                          $revCounter++;
 349                          if ($revCounter == abs($direction)) {
 350                              $relativeRev = $info['date'];
 351                          }
 352                      }
 353                  }
 354              }
 355  
 356              //true when $rev is found, but not the wanted follow-up.
 357              $checkOtherChunk = $fp
 358                  && ($info['date'] == $rev || ($revCounter > 0 && !$relativeRev))
 359                  && !(($tail == $eof && $direction > 0) || ($head == 0 && $direction < 0));
 360  
 361              if ($checkOtherChunk) {
 362                  list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, $direction);
 363  
 364                  if (empty($lines)) break;
 365              }
 366          }
 367          if ($fp) {
 368              fclose($fp);
 369          }
 370  
 371          return $relativeRev;
 372      }
 373  
 374      /**
 375       * Returns revisions around rev1 and rev2
 376       * When available it returns $max entries for each revision
 377       *
 378       * @param int $rev1 oldest revision timestamp
 379       * @param int $rev2 newest revision timestamp (0 looks up last revision)
 380       * @param int $max maximum number of revisions returned
 381       * @return array with two arrays with revisions surrounding rev1 respectively rev2
 382       */
 383      public function getRevisionsAround($rev1, $rev2, $max = 50)
 384      {
 385          $max = intval(abs($max) / 2) * 2 + 1;
 386          $rev1 = max($rev1, 0);
 387          $rev2 = max($rev2, 0);
 388  
 389          if ($rev2) {
 390              if ($rev2 < $rev1) {
 391                  $rev = $rev2;
 392                  $rev2 = $rev1;
 393                  $rev1 = $rev;
 394              }
 395          } else {
 396              //empty right side means a removed page. Look up last revision.
 397              $rev2 = $this->currentRevision();
 398          }
 399          //collect revisions around rev2
 400          list($revs2, $allRevs, $fp, $lines, $head, $tail) = $this->retrieveRevisionsAround($rev2, $max);
 401  
 402          if (empty($revs2)) return array(array(), array());
 403  
 404          //collect revisions around rev1
 405          $index = array_search($rev1, $allRevs);
 406          if ($index === false) {
 407              //no overlapping revisions
 408              list($revs1, , , , ,) = $this->retrieveRevisionsAround($rev1, $max);
 409              if (empty($revs1)) $revs1 = array();
 410          } else {
 411              //revisions overlaps, reuse revisions around rev2
 412              $lastRev = array_pop($allRevs); //keep last entry that could be external edit
 413              $revs1 = $allRevs;
 414              while ($head > 0) {
 415                  for ($i = count($lines) - 1; $i >= 0; $i--) {
 416                      $info = $this->parseLogLine($lines[$i]);
 417                      if ($this->cacheRevisionInfo($info)) {
 418                          $revs1[] = $info['date'];
 419                          $index++;
 420  
 421                          if ($index > intval($max / 2)) break 2;
 422                      }
 423                  }
 424  
 425                  list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1);
 426              }
 427              sort($revs1);
 428              $revs1[] = $lastRev; //push back last entry
 429  
 430              //return wanted selection
 431              $revs1 = array_slice($revs1, max($index - intval($max / 2), 0), $max);
 432          }
 433  
 434          return array(array_reverse($revs1), array_reverse($revs2));
 435      }
 436  
 437      /**
 438       * Return an existing revision for a specific date which is
 439       * the current one or younger or equal then the date
 440       *
 441       * @param number $date_at timestamp
 442       * @return string revision ('' for current)
 443       */
 444      public function getLastRevisionAt($date_at)
 445      {
 446          $fileLastMod = $this->getFilename();
 447          //requested date_at(timestamp) younger or equal then modified_time($this->id) => load current
 448          if (file_exists($fileLastMod) && $date_at >= @filemtime($fileLastMod)) {
 449              return '';
 450          } else {
 451              if ($rev = $this->getRelativeRevision($date_at + 1, -1)) { //+1 to get also the requested date revision
 452                  return $rev;
 453              } else {
 454                  return false;
 455              }
 456          }
 457      }
 458  
 459      /**
 460       * Collect the $max revisions near to the timestamp $rev
 461       *
 462       * Ideally, half of retrieved timestamps are older than $rev, another half are newer.
 463       * The returned array $requestedRevs may not contain the reference timestamp $rev
 464       * when it does not match any revision value recorded in changelog.
 465       *
 466       * @param int $rev revision timestamp
 467       * @param int $max maximum number of revisions to be returned
 468       * @return bool|array
 469       *     return array with entries:
 470       *       - $requestedRevs: array of with $max revision timestamps
 471       *       - $revs: all parsed revision timestamps
 472       *       - $fp: file pointer only defined for chuck reading, needs closing.
 473       *       - $lines: non-parsed changelog lines before the parsed revisions
 474       *       - $head: position of first read changelog line
 475       *       - $lastTail: position of end of last read changelog line
 476       *     otherwise false
 477       */
 478      protected function retrieveRevisionsAround($rev, $max)
 479      {
 480          $revs = array();
 481          $afterCount = $beforeCount = 0;
 482  
 483          //get lines from changelog
 484          list($fp, $lines, $startHead, $startTail, $eof) = $this->readloglines($rev);
 485          if (empty($lines)) return false;
 486  
 487          //parse changelog lines in chunk, and read forward more chunks until $max/2 is reached
 488          $head = $startHead;
 489          $tail = $startTail;
 490          while (count($lines) > 0) {
 491              foreach ($lines as $line) {
 492                  $info = $this->parseLogLine($line);
 493                  if ($this->cacheRevisionInfo($info)) {
 494                      $revs[] = $info['date'];
 495                      if ($info['date'] >= $rev) {
 496                          //count revs after reference $rev
 497                          $afterCount++;
 498                          if ($afterCount == 1) $beforeCount = count($revs);
 499                      }
 500                      //enough revs after reference $rev?
 501                      if ($afterCount > intval($max / 2)) break 2;
 502                  }
 503              }
 504              //retrieve next chunk
 505              list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, 1);
 506          }
 507          $lastTail = $tail;
 508  
 509          // add a possible revision of external edit, create or deletion
 510          if ($lastTail == $eof && $afterCount <= intval($max / 2) &&
 511              count($revs) && !$this->isCurrentRevision($revs[count($revs)-1])
 512          ) {
 513              $revs[] = $this->currentRevision;
 514              $afterCount++;
 515          }
 516  
 517          if ($afterCount == 0) {
 518              //given timestamp $rev is newer than the most recent line in chunk
 519              return false; //FIXME: or proceed to collect older revisions?
 520          }
 521  
 522          //read more chunks backward until $max/2 is reached and total number of revs is equal to $max
 523          $lines = array();
 524          $i = 0;
 525          if ($afterCount > 0) {
 526              $head = $startHead;
 527              $tail = $startTail;
 528              while ($head > 0) {
 529                  list($lines, $head, $tail) = $this->readAdjacentChunk($fp, $head, $tail, -1);
 530  
 531                  for ($i = count($lines) - 1; $i >= 0; $i--) {
 532                      $info = $this->parseLogLine($lines[$i]);
 533                      if ($this->cacheRevisionInfo($info)) {
 534                          $revs[] = $info['date'];
 535                          $beforeCount++;
 536                          //enough revs before reference $rev?
 537                          if ($beforeCount > max(intval($max / 2), $max - $afterCount)) break 2;
 538                      }
 539                  }
 540              }
 541          }
 542          //keep only non-parsed lines
 543          $lines = array_slice($lines, 0, $i);
 544  
 545          sort($revs);
 546  
 547          //trunk desired selection
 548          $requestedRevs = array_slice($revs, -$max, $max);
 549  
 550          return array($requestedRevs, $revs, $fp, $lines, $head, $lastTail);
 551      }
 552  
 553      /**
 554       * Get the current revision information, considering external edit, create or deletion
 555       *
 556       * When the file has not modified since its last revision, the information of the last
 557       * change that had already recorded in the changelog is returned as current change info.
 558       * Otherwise, the change information since the last revision caused outside DokuWiki
 559       * should be returned, which is referred as "external revision".
 560       *
 561       * The change date of the file can be determined by timestamp as far as the file exists,
 562       * however this is not possible when the file has already deleted outside of DokuWiki.
 563       * In such case we assign 1 sec before current time() for the external deletion.
 564       * As a result, the value of current revision identifier may change each time because:
 565       *   1) the file has again modified outside of DokuWiki, or
 566       *   2) the value is essentially volatile for deleted but once existed files.
 567       *
 568       * @return bool|array false when page had never existed or array with entries:
 569       *      - date:  revision identifier (timestamp or last revision +1)
 570       *      - ip:    IPv4 address (127.0.0.1)
 571       *      - type:  log line type
 572       *      - id:    id of page or media
 573       *      - user:  user name
 574       *      - sum:   edit summary (or action reason)
 575       *      - extra: extra data (varies by line type)
 576       *      - sizechange: change of filesize
 577       *      - timestamp: unix timestamp or false (key set only for external edit occurred)
 578       *
 579       * @author  Satoshi Sahara <sahara.satoshi@gmail.com>
 580       */
 581      public function getCurrentRevisionInfo()
 582      {
 583          global $lang;
 584  
 585          if (isset($this->currentRevision)) return $this->getRevisionInfo($this->currentRevision);
 586  
 587          // get revision id from the item file timestamp and changelog
 588          $fileLastMod = $this->getFilename();
 589          $fileRev = @filemtime($fileLastMod); // false when the file not exist
 590          $lastRev = $this->lastRevision();    // false when no changelog
 591  
 592          if (!$fileRev && !$lastRev) {                // has never existed
 593              $this->currentRevision = false;
 594              return false;
 595          } elseif ($fileRev === $lastRev) {           // not external edit
 596              $this->currentRevision = $lastRev;
 597              return $this->getRevisionInfo($lastRev);
 598          }
 599  
 600          if (!$fileRev && $lastRev) {                 // item file does not exist
 601              // check consistency against changelog
 602              $revInfo = $this->getRevisionInfo($lastRev, false);
 603              if ($revInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
 604                  $this->currentRevision = $lastRev;
 605                  return $revInfo;
 606              }
 607  
 608              // externally deleted, set revision date as late as possible
 609              $revInfo = [
 610                  'date' => max($lastRev +1, time() -1), // 1 sec before now or new page save
 611                  'ip'   => '127.0.0.1',
 612                  'type' => DOKU_CHANGE_TYPE_DELETE,
 613                  'id'   => $this->id,
 614                  'user' => '',
 615                  'sum'  => $lang['deleted'].' - '.$lang['external_edit'].' ('.$lang['unknowndate'].')',
 616                  'extra' => '',
 617                  'sizechange' => -io_getSizeFile($this->getFilename($lastRev)),
 618                  'timestamp' => false,
 619              ];
 620  
 621          } else {                                     // item file exists, with timestamp $fileRev
 622              // here, file timestamp $fileRev is different with last revision timestamp $lastRev in changelog
 623              $isJustCreated = $lastRev === false || (
 624                      $fileRev > $lastRev &&
 625                      $this->getRevisionInfo($lastRev, false)['type'] == DOKU_CHANGE_TYPE_DELETE
 626              );
 627              $filesize_new = filesize($this->getFilename());
 628              $filesize_old = $isJustCreated ? 0 : io_getSizeFile($this->getFilename($lastRev));
 629              $sizechange = $filesize_new - $filesize_old;
 630  
 631              if ($isJustCreated) {
 632                  $timestamp = $fileRev;
 633                  $sum = $lang['created'].' - '.$lang['external_edit'];
 634              } elseif ($fileRev > $lastRev) {
 635                  $timestamp = $fileRev;
 636                  $sum = $lang['external_edit'];
 637              } else {
 638                  // $fileRev is older than $lastRev, that is erroneous/incorrect occurrence.
 639                  $msg = "Warning: current file modification time is older than last revision date";
 640                  $details = 'File revision: '.$fileRev.' '.dformat($fileRev, "%Y-%m-%d %H:%M:%S")."\n"
 641                            .'Last revision: '.$lastRev.' '.dformat($lastRev, "%Y-%m-%d %H:%M:%S");
 642                  Logger::error($msg, $details, $this->getFilename());
 643                  $timestamp = false;
 644                  $sum = $lang['external_edit'].' ('.$lang['unknowndate'].')';
 645              }
 646  
 647              // externally created or edited
 648              $revInfo = [
 649                  'date' => $timestamp ?: $lastRev +1,
 650                  'ip'   => '127.0.0.1',
 651                  'type' => $isJustCreated ? DOKU_CHANGE_TYPE_CREATE : DOKU_CHANGE_TYPE_EDIT,
 652                  'id'   => $this->id,
 653                  'user' => '',
 654                  'sum'  => $sum,
 655                  'extra' => '',
 656                  'sizechange' => $sizechange,
 657                  'timestamp' => $timestamp,
 658              ];
 659          }
 660  
 661          // cache current revision information of external edition
 662          $this->currentRevision = $revInfo['date'];
 663          $this->cache[$this->id][$this->currentRevision] = $revInfo;
 664          return $this->getRevisionInfo($this->currentRevision);
 665      }
 666  
 667      /**
 668       * Mechanism to trace no-actual external current revision
 669       * @param int $rev
 670       */
 671      public function traceCurrentRevision($rev)
 672      {
 673          if ($rev > $this->lastRevision()) {
 674              $rev = $this->currentRevision();
 675          }
 676          return $rev;
 677      }
 678  }