changelog.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <?php
  2. /**
  3. * Changelog handling functions
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Andreas Gohr <andi@splitbrain.org>
  7. */
  8. use dokuwiki\ChangeLog\ChangeLog;
  9. use dokuwiki\File\PageFile;
  10. /**
  11. * parses a changelog line into it's components
  12. *
  13. * @author Ben Coburn <btcoburn@silicodon.net>
  14. *
  15. * @param string $line changelog line
  16. * @return array|bool parsed line or false
  17. */
  18. function parseChangelogLine($line) {
  19. return ChangeLog::parseLogLine($line);
  20. }
  21. /**
  22. * Adds an entry to the changelog and saves the metadata for the page
  23. *
  24. * Note: timestamp of the change might not be unique especially after very quick
  25. * repeated edits (e.g. change checkbox via do plugin)
  26. *
  27. * @param int $date Timestamp of the change
  28. * @param String $id Name of the affected page
  29. * @param String $type Type of the change see DOKU_CHANGE_TYPE_*
  30. * @param String $summary Summary of the change
  31. * @param mixed $extra In case of a revert the revision (timestamp) of the reverted page
  32. * @param array $flags Additional flags in a key value array.
  33. * Available flags:
  34. * - ExternalEdit - mark as an external edit.
  35. * @param null|int $sizechange Change of filesize
  36. *
  37. * @author Andreas Gohr <andi@splitbrain.org>
  38. * @author Esther Brunner <wikidesign@gmail.com>
  39. * @author Ben Coburn <btcoburn@silicodon.net>
  40. * @deprecated 2021-11-28
  41. */
  42. function addLogEntry(
  43. $date,
  44. $id,
  45. $type = DOKU_CHANGE_TYPE_EDIT,
  46. $summary = '',
  47. $extra = '',
  48. $flags = null,
  49. $sizechange = null)
  50. {
  51. // no more used in DokuWiki core, but left for third-party plugins
  52. dbg_deprecated('see '. PageFile::class .'::saveWikiText()');
  53. /** @var Input $INPUT */
  54. global $INPUT;
  55. // check for special flags as keys
  56. if (!is_array($flags)) $flags = array();
  57. $flagExternalEdit = isset($flags['ExternalEdit']);
  58. $id = cleanid($id);
  59. if (!$date) $date = time(); //use current time if none supplied
  60. $remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1';
  61. $user = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : '';
  62. $sizechange = ($sizechange === null) ? '' : (int)$sizechange;
  63. // update changelog file and get the added entry that is also to be stored in metadata
  64. $pageFile = new PageFile($id);
  65. $logEntry = $pageFile->changelog->addLogEntry([
  66. 'date' => $date,
  67. 'ip' => $remote,
  68. 'type' => $type,
  69. 'id' => $id,
  70. 'user' => $user,
  71. 'sum' => $summary,
  72. 'extra' => $extra,
  73. 'sizechange' => $sizechange,
  74. ]);
  75. // update metadata
  76. $pageFile->updateMetadata($logEntry);
  77. }
  78. /**
  79. * Adds an entry to the media changelog
  80. *
  81. * @author Michael Hamann <michael@content-space.de>
  82. * @author Andreas Gohr <andi@splitbrain.org>
  83. * @author Esther Brunner <wikidesign@gmail.com>
  84. * @author Ben Coburn <btcoburn@silicodon.net>
  85. *
  86. * @param int $date Timestamp of the change
  87. * @param String $id Name of the affected page
  88. * @param String $type Type of the change see DOKU_CHANGE_TYPE_*
  89. * @param String $summary Summary of the change
  90. * @param mixed $extra In case of a revert the revision (timestamp) of the reverted page
  91. * @param array $flags Additional flags in a key value array.
  92. * Available flags:
  93. * - (none, so far)
  94. * @param null|int $sizechange Change of filesize
  95. */
  96. function addMediaLogEntry(
  97. $date,
  98. $id,
  99. $type = DOKU_CHANGE_TYPE_EDIT,
  100. $summary = '',
  101. $extra = '',
  102. $flags = null,
  103. $sizechange = null)
  104. {
  105. /** @var Input $INPUT */
  106. global $INPUT;
  107. // check for special flags as keys
  108. if (!is_array($flags)) $flags = array();
  109. $flagExternalEdit = isset($flags['ExternalEdit']);
  110. $id = cleanid($id);
  111. if (!$date) $date = time(); //use current time if none supplied
  112. $remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1';
  113. $user = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : '';
  114. $sizechange = ($sizechange === null) ? '' : (int)$sizechange;
  115. // update changelog file and get the added entry
  116. (new \dokuwiki\ChangeLog\MediaChangeLog($id, 1024))->addLogEntry([
  117. 'date' => $date,
  118. 'ip' => $remote,
  119. 'type' => $type,
  120. 'id' => $id,
  121. 'user' => $user,
  122. 'sum' => $summary,
  123. 'extra' => $extra,
  124. 'sizechange' => $sizechange,
  125. ]);
  126. }
  127. /**
  128. * returns an array of recently changed files using the changelog
  129. *
  130. * The following constants can be used to control which changes are
  131. * included. Add them together as needed.
  132. *
  133. * RECENTS_SKIP_DELETED - don't include deleted pages
  134. * RECENTS_SKIP_MINORS - don't include minor changes
  135. * RECENTS_ONLY_CREATION - only include new created pages and media
  136. * RECENTS_SKIP_SUBSPACES - don't include subspaces
  137. * RECENTS_MEDIA_CHANGES - return media changes instead of page changes
  138. * RECENTS_MEDIA_PAGES_MIXED - return both media changes and page changes
  139. *
  140. * @param int $first number of first entry returned (for paginating
  141. * @param int $num return $num entries
  142. * @param string $ns restrict to given namespace
  143. * @param int $flags see above
  144. * @return array recently changed files
  145. *
  146. * @author Ben Coburn <btcoburn@silicodon.net>
  147. * @author Kate Arzamastseva <pshns@ukr.net>
  148. */
  149. function getRecents($first, $num, $ns = '', $flags = 0) {
  150. global $conf;
  151. $recent = array();
  152. $count = 0;
  153. if (!$num)
  154. return $recent;
  155. // read all recent changes. (kept short)
  156. if ($flags & RECENTS_MEDIA_CHANGES) {
  157. $lines = @file($conf['media_changelog']) ?: [];
  158. } else {
  159. $lines = @file($conf['changelog']) ?: [];
  160. }
  161. if (!is_array($lines)) {
  162. $lines = array();
  163. }
  164. $lines_position = count($lines) - 1;
  165. $media_lines_position = 0;
  166. $media_lines = array();
  167. if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
  168. $media_lines = @file($conf['media_changelog']) ?: [];
  169. if (!is_array($media_lines)) {
  170. $media_lines = array();
  171. }
  172. $media_lines_position = count($media_lines) - 1;
  173. }
  174. $seen = array(); // caches seen lines, _handleRecent() skips them
  175. // handle lines
  176. while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >= 0)) {
  177. if (empty($rec) && $lines_position >= 0) {
  178. $rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen);
  179. if (!$rec) {
  180. $lines_position --;
  181. continue;
  182. }
  183. }
  184. if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
  185. $media_rec = _handleRecent(
  186. @$media_lines[$media_lines_position],
  187. $ns,
  188. $flags | RECENTS_MEDIA_CHANGES,
  189. $seen
  190. );
  191. if (!$media_rec) {
  192. $media_lines_position --;
  193. continue;
  194. }
  195. }
  196. if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
  197. $media_lines_position--;
  198. $x = $media_rec;
  199. $x['media'] = true;
  200. $media_rec = false;
  201. } else {
  202. $lines_position--;
  203. $x = $rec;
  204. if ($flags & RECENTS_MEDIA_CHANGES){
  205. $x['media'] = true;
  206. } else {
  207. $x['media'] = false;
  208. }
  209. $rec = false;
  210. }
  211. if (--$first >= 0) continue; // skip first entries
  212. $recent[] = $x;
  213. $count++;
  214. // break when we have enough entries
  215. if ($count >= $num) { break; }
  216. }
  217. return $recent;
  218. }
  219. /**
  220. * returns an array of files changed since a given time using the
  221. * changelog
  222. *
  223. * The following constants can be used to control which changes are
  224. * included. Add them together as needed.
  225. *
  226. * RECENTS_SKIP_DELETED - don't include deleted pages
  227. * RECENTS_SKIP_MINORS - don't include minor changes
  228. * RECENTS_ONLY_CREATION - only include new created pages and media
  229. * RECENTS_SKIP_SUBSPACES - don't include subspaces
  230. * RECENTS_MEDIA_CHANGES - return media changes instead of page changes
  231. *
  232. * @param int $from date of the oldest entry to return
  233. * @param int $to date of the newest entry to return (for pagination, optional)
  234. * @param string $ns restrict to given namespace (optional)
  235. * @param int $flags see above (optional)
  236. * @return array of files
  237. *
  238. * @author Michael Hamann <michael@content-space.de>
  239. * @author Ben Coburn <btcoburn@silicodon.net>
  240. */
  241. function getRecentsSince($from, $to = null, $ns = '', $flags = 0) {
  242. global $conf;
  243. $recent = array();
  244. if ($to && $to < $from)
  245. return $recent;
  246. // read all recent changes. (kept short)
  247. if ($flags & RECENTS_MEDIA_CHANGES) {
  248. $lines = @file($conf['media_changelog']);
  249. } else {
  250. $lines = @file($conf['changelog']);
  251. }
  252. if (!$lines) return $recent;
  253. // we start searching at the end of the list
  254. $lines = array_reverse($lines);
  255. // handle lines
  256. $seen = array(); // caches seen lines, _handleRecent() skips them
  257. foreach ($lines as $line) {
  258. $rec = _handleRecent($line, $ns, $flags, $seen);
  259. if ($rec !== false) {
  260. if ($rec['date'] >= $from) {
  261. if (!$to || $rec['date'] <= $to) {
  262. $recent[] = $rec;
  263. }
  264. } else {
  265. break;
  266. }
  267. }
  268. }
  269. return array_reverse($recent);
  270. }
  271. /**
  272. * Internal function used by getRecents
  273. *
  274. * don't call directly
  275. *
  276. * @see getRecents()
  277. * @author Andreas Gohr <andi@splitbrain.org>
  278. * @author Ben Coburn <btcoburn@silicodon.net>
  279. *
  280. * @param string $line changelog line
  281. * @param string $ns restrict to given namespace
  282. * @param int $flags flags to control which changes are included
  283. * @param array $seen listing of seen pages
  284. * @return array|bool false or array with info about a change
  285. */
  286. function _handleRecent($line, $ns, $flags, &$seen) {
  287. if (empty($line)) return false; //skip empty lines
  288. // split the line into parts
  289. $recent = ChangeLog::parseLogLine($line);
  290. if ($recent === false) return false;
  291. // skip seen ones
  292. if (isset($seen[$recent['id']])) return false;
  293. // skip changes, of only new items are requested
  294. if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false;
  295. // skip minors
  296. if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
  297. // remember in seen to skip additional sights
  298. $seen[$recent['id']] = 1;
  299. // check if it's a hidden page
  300. if (isHiddenPage($recent['id'])) return false;
  301. // filter namespace
  302. if (($ns) && (strpos($recent['id'], $ns.':') !== 0)) return false;
  303. // exclude subnamespaces
  304. if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
  305. // check ACL
  306. if ($flags & RECENTS_MEDIA_CHANGES) {
  307. $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
  308. } else {
  309. $recent['perms'] = auth_quickaclcheck($recent['id']);
  310. }
  311. if ($recent['perms'] < AUTH_READ) return false;
  312. // check existence
  313. if ($flags & RECENTS_SKIP_DELETED) {
  314. $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
  315. if (!file_exists($fn)) return false;
  316. }
  317. return $recent;
  318. }