You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

373 lines
12 KiB

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