ChangeLog.php 25 KB

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