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