Move defines to their own file
[dokuwiki.git] / inc / changelog.php
blobf02572e7f2e2cbdfe7393671afa34812e63e97ba
1 <?php
2 /**
3 * Changelog handling functions
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
7 */
9 /**
10 * parses a changelog line into it's components
12 * @author Ben Coburn <btcoburn@silicodon.net>
14 * @param string $line changelog line
15 * @return array|bool parsed line or false
17 function parseChangelogLine($line) {
18 $line = rtrim($line, "\n");
19 $tmp = explode("\t", $line);
20 if ($tmp!==false && count($tmp)>1) {
21 $info = array();
22 $info['date'] = (int)$tmp[0]; // unix timestamp
23 $info['ip'] = $tmp[1]; // IPv4 address (127.0.0.1)
24 $info['type'] = $tmp[2]; // log line type
25 $info['id'] = $tmp[3]; // page id
26 $info['user'] = $tmp[4]; // user name
27 $info['sum'] = $tmp[5]; // edit summary (or action reason)
28 $info['extra'] = $tmp[6]; // extra data (varies by line type)
29 if(isset($tmp[7]) && $tmp[7] !== '') { //last item has line-end||
30 $info['sizechange'] = (int) $tmp[7];
31 } else {
32 $info['sizechange'] = null;
34 return $info;
35 } else {
36 return false;
40 /**
41 * Add's an entry to the changelog and saves the metadata for the page
43 * @param int $date Timestamp of the change
44 * @param String $id Name of the affected page
45 * @param String $type Type of the change see DOKU_CHANGE_TYPE_*
46 * @param String $summary Summary of the change
47 * @param mixed $extra In case of a revert the revision (timestmp) of the reverted page
48 * @param array $flags Additional flags in a key value array.
49 * Available flags:
50 * - ExternalEdit - mark as an external edit.
51 * @param null|int $sizechange Change of filesize
53 * @author Andreas Gohr <andi@splitbrain.org>
54 * @author Esther Brunner <wikidesign@gmail.com>
55 * @author Ben Coburn <btcoburn@silicodon.net>
57 function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null, $sizechange = null){
58 global $conf, $INFO;
59 /** @var Input $INPUT */
60 global $INPUT;
62 // check for special flags as keys
63 if (!is_array($flags)) { $flags = array(); }
64 $flagExternalEdit = isset($flags['ExternalEdit']);
66 $id = cleanid($id);
67 $file = wikiFN($id);
68 $created = @filectime($file);
69 $minor = ($type===DOKU_CHANGE_TYPE_MINOR_EDIT);
70 $wasRemoved = ($type===DOKU_CHANGE_TYPE_DELETE);
72 if(!$date) $date = time(); //use current time if none supplied
73 $remote = (!$flagExternalEdit)?clientIP(true):'127.0.0.1';
74 $user = (!$flagExternalEdit)?$INPUT->server->str('REMOTE_USER'):'';
75 if($sizechange === null) {
76 $sizechange = '';
77 } else {
78 $sizechange = (int) $sizechange;
81 $strip = array("\t", "\n");
82 $logline = array(
83 'date' => $date,
84 'ip' => $remote,
85 'type' => str_replace($strip, '', $type),
86 'id' => $id,
87 'user' => $user,
88 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
89 'extra' => str_replace($strip, '', $extra),
90 'sizechange' => $sizechange
93 $wasCreated = ($type===DOKU_CHANGE_TYPE_CREATE);
94 $wasReverted = ($type===DOKU_CHANGE_TYPE_REVERT);
95 // update metadata
96 if (!$wasRemoved) {
97 $oldmeta = p_read_metadata($id);
98 $meta = array();
99 if (
100 $wasCreated && (
101 empty($oldmeta['persistent']['date']['created']) ||
102 $oldmeta['persistent']['date']['created'] === $created
105 // newly created
106 $meta['date']['created'] = $created;
107 if ($user){
108 $meta['creator'] = isset($INFO) ? $INFO['userinfo']['name'] : null;
109 $meta['user'] = $user;
111 } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['persistent']['date']['created'])) {
112 // re-created / restored
113 $meta['date']['created'] = $oldmeta['persistent']['date']['created'];
114 $meta['date']['modified'] = $created; // use the files ctime here
115 $meta['creator'] = $oldmeta['persistent']['creator'];
116 if ($user) $meta['contributor'][$user] = isset($INFO) ? $INFO['userinfo']['name'] : null;
117 } elseif (!$minor) { // non-minor modification
118 $meta['date']['modified'] = $date;
119 if ($user) $meta['contributor'][$user] = isset($INFO) ? $INFO['userinfo']['name'] : null;
121 $meta['last_change'] = $logline;
122 p_set_metadata($id, $meta);
125 // add changelog lines
126 $logline = implode("\t", $logline)."\n";
127 io_saveFile(metaFN($id,'.changes'),$logline,true); //page changelog
128 io_saveFile($conf['changelog'],$logline,true); //global changelog cache
132 * Add's an entry to the media changelog
134 * @author Michael Hamann <michael@content-space.de>
135 * @author Andreas Gohr <andi@splitbrain.org>
136 * @author Esther Brunner <wikidesign@gmail.com>
137 * @author Ben Coburn <btcoburn@silicodon.net>
139 * @param int $date Timestamp of the change
140 * @param String $id Name of the affected page
141 * @param String $type Type of the change see DOKU_CHANGE_TYPE_*
142 * @param String $summary Summary of the change
143 * @param mixed $extra In case of a revert the revision (timestmp) of the reverted page
144 * @param array $flags Additional flags in a key value array.
145 * Available flags:
146 * - (none, so far)
147 * @param null|int $sizechange Change of filesize
149 function addMediaLogEntry(
150 $date,
151 $id,
152 $type=DOKU_CHANGE_TYPE_EDIT,
153 $summary='',
154 $extra='',
155 $flags=null,
156 $sizechange = null)
158 global $conf;
159 /** @var Input $INPUT */
160 global $INPUT;
162 $id = cleanid($id);
164 if(!$date) $date = time(); //use current time if none supplied
165 $remote = clientIP(true);
166 $user = $INPUT->server->str('REMOTE_USER');
167 if($sizechange === null) {
168 $sizechange = '';
169 } else {
170 $sizechange = (int) $sizechange;
173 $strip = array("\t", "\n");
174 $logline = array(
175 'date' => $date,
176 'ip' => $remote,
177 'type' => str_replace($strip, '', $type),
178 'id' => $id,
179 'user' => $user,
180 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
181 'extra' => str_replace($strip, '', $extra),
182 'sizechange' => $sizechange
185 // add changelog lines
186 $logline = implode("\t", $logline)."\n";
187 io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache
188 io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog
192 * returns an array of recently changed files using the
193 * changelog
195 * The following constants can be used to control which changes are
196 * included. Add them together as needed.
198 * RECENTS_SKIP_DELETED - don't include deleted pages
199 * RECENTS_SKIP_MINORS - don't include minor changes
200 * RECENTS_ONLY_CREATION - only include new created pages and media
201 * RECENTS_SKIP_SUBSPACES - don't include subspaces
202 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes
203 * RECENTS_MEDIA_PAGES_MIXED - return both media changes and page changes
205 * @param int $first number of first entry returned (for paginating
206 * @param int $num return $num entries
207 * @param string $ns restrict to given namespace
208 * @param int $flags see above
209 * @return array recently changed files
211 * @author Ben Coburn <btcoburn@silicodon.net>
212 * @author Kate Arzamastseva <pshns@ukr.net>
214 function getRecents($first,$num,$ns='',$flags=0){
215 global $conf;
216 $recent = array();
217 $count = 0;
219 if(!$num)
220 return $recent;
222 // read all recent changes. (kept short)
223 if ($flags & RECENTS_MEDIA_CHANGES) {
224 $lines = @file($conf['media_changelog']) ?: [];
225 } else {
226 $lines = @file($conf['changelog']) ?: [];
228 if (!is_array($lines)) {
229 $lines = array();
231 $lines_position = count($lines)-1;
232 $media_lines_position = 0;
233 $media_lines = array();
235 if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
236 $media_lines = @file($conf['media_changelog']) ?: [];
237 if (!is_array($media_lines)) {
238 $media_lines = array();
240 $media_lines_position = count($media_lines)-1;
243 $seen = array(); // caches seen lines, _handleRecent() skips them
245 // handle lines
246 while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) {
247 if (empty($rec) && $lines_position >= 0) {
248 $rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen);
249 if (!$rec) {
250 $lines_position --;
251 continue;
254 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
255 $media_rec = _handleRecent(
256 @$media_lines[$media_lines_position],
257 $ns,
258 $flags | RECENTS_MEDIA_CHANGES,
259 $seen
261 if (!$media_rec) {
262 $media_lines_position --;
263 continue;
266 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
267 $media_lines_position--;
268 $x = $media_rec;
269 $x['media'] = true;
270 $media_rec = false;
271 } else {
272 $lines_position--;
273 $x = $rec;
274 if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
275 $rec = false;
277 if(--$first >= 0) continue; // skip first entries
278 $recent[] = $x;
279 $count++;
280 // break when we have enough entries
281 if($count >= $num){ break; }
283 return $recent;
287 * returns an array of files changed since a given time using the
288 * changelog
290 * The following constants can be used to control which changes are
291 * included. Add them together as needed.
293 * RECENTS_SKIP_DELETED - don't include deleted pages
294 * RECENTS_SKIP_MINORS - don't include minor changes
295 * RECENTS_ONLY_CREATION - only include new created pages and media
296 * RECENTS_SKIP_SUBSPACES - don't include subspaces
297 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes
299 * @param int $from date of the oldest entry to return
300 * @param int $to date of the newest entry to return (for pagination, optional)
301 * @param string $ns restrict to given namespace (optional)
302 * @param int $flags see above (optional)
303 * @return array of files
305 * @author Michael Hamann <michael@content-space.de>
306 * @author Ben Coburn <btcoburn@silicodon.net>
308 function getRecentsSince($from,$to=null,$ns='',$flags=0){
309 global $conf;
310 $recent = array();
312 if($to && $to < $from)
313 return $recent;
315 // read all recent changes. (kept short)
316 if ($flags & RECENTS_MEDIA_CHANGES) {
317 $lines = @file($conf['media_changelog']);
318 } else {
319 $lines = @file($conf['changelog']);
321 if(!$lines) return $recent;
323 // we start searching at the end of the list
324 $lines = array_reverse($lines);
326 // handle lines
327 $seen = array(); // caches seen lines, _handleRecent() skips them
329 foreach($lines as $line){
330 $rec = _handleRecent($line, $ns, $flags, $seen);
331 if($rec !== false) {
332 if ($rec['date'] >= $from) {
333 if (!$to || $rec['date'] <= $to) {
334 $recent[] = $rec;
336 } else {
337 break;
342 return array_reverse($recent);
346 * Internal function used by getRecents
348 * don't call directly
350 * @see getRecents()
351 * @author Andreas Gohr <andi@splitbrain.org>
352 * @author Ben Coburn <btcoburn@silicodon.net>
354 * @param string $line changelog line
355 * @param string $ns restrict to given namespace
356 * @param int $flags flags to control which changes are included
357 * @param array $seen listing of seen pages
358 * @return array|bool false or array with info about a change
360 function _handleRecent($line,$ns,$flags,&$seen){
361 if(empty($line)) return false; //skip empty lines
363 // split the line into parts
364 $recent = parseChangelogLine($line);
365 if ($recent===false) { return false; }
367 // skip seen ones
368 if(isset($seen[$recent['id']])) return false;
370 // skip changes, of only new items are requested
371 if($recent['type']!==DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false;
373 // skip minors
374 if($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
376 // remember in seen to skip additional sights
377 $seen[$recent['id']] = 1;
379 // check if it's a hidden page
380 if(isHiddenPage($recent['id'])) return false;
382 // filter namespace
383 if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false;
385 // exclude subnamespaces
386 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
388 // check ACL
389 if ($flags & RECENTS_MEDIA_CHANGES) {
390 $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
391 } else {
392 $recent['perms'] = auth_quickaclcheck($recent['id']);
394 if ($recent['perms'] < AUTH_READ) return false;
396 // check existance
397 if($flags & RECENTS_SKIP_DELETED){
398 $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
399 if(!file_exists($fn)) return false;
402 return $recent;