replaced deprecated utf8 functions
[dokuwiki.git] / inc / changelog.php
blob60c49c2ee7e4382ebfafdfc8bf435bfb9e8c9386
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 // Constants for known core changelog line types.
10 // Use these in place of string literals for more readable code.
11 define('DOKU_CHANGE_TYPE_CREATE', 'C');
12 define('DOKU_CHANGE_TYPE_EDIT', 'E');
13 define('DOKU_CHANGE_TYPE_MINOR_EDIT', 'e');
14 define('DOKU_CHANGE_TYPE_DELETE', 'D');
15 define('DOKU_CHANGE_TYPE_REVERT', 'R');
17 /**
18 * parses a changelog line into it's components
20 * @author Ben Coburn <btcoburn@silicodon.net>
22 * @param string $line changelog line
23 * @return array|bool parsed line or false
25 function parseChangelogLine($line) {
26 $line = rtrim($line, "\n");
27 $tmp = explode("\t", $line);
28 if ($tmp!==false && count($tmp)>1) {
29 $info = array();
30 $info['date'] = (int)$tmp[0]; // unix timestamp
31 $info['ip'] = $tmp[1]; // IPv4 address (127.0.0.1)
32 $info['type'] = $tmp[2]; // log line type
33 $info['id'] = $tmp[3]; // page id
34 $info['user'] = $tmp[4]; // user name
35 $info['sum'] = $tmp[5]; // edit summary (or action reason)
36 $info['extra'] = $tmp[6]; // extra data (varies by line type)
37 if(isset($tmp[7]) && $tmp[7] !== '') { //last item has line-end||
38 $info['sizechange'] = (int) $tmp[7];
39 } else {
40 $info['sizechange'] = null;
42 return $info;
43 } else {
44 return false;
48 /**
49 * Add's an entry to the changelog and saves the metadata for the page
51 * @param int $date Timestamp of the change
52 * @param String $id Name of the affected page
53 * @param String $type Type of the change see DOKU_CHANGE_TYPE_*
54 * @param String $summary Summary of the change
55 * @param mixed $extra In case of a revert the revision (timestmp) of the reverted page
56 * @param array $flags Additional flags in a key value array.
57 * Available flags:
58 * - ExternalEdit - mark as an external edit.
59 * @param null|int $sizechange Change of filesize
61 * @author Andreas Gohr <andi@splitbrain.org>
62 * @author Esther Brunner <wikidesign@gmail.com>
63 * @author Ben Coburn <btcoburn@silicodon.net>
65 function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null, $sizechange = null){
66 global $conf, $INFO;
67 /** @var Input $INPUT */
68 global $INPUT;
70 // check for special flags as keys
71 if (!is_array($flags)) { $flags = array(); }
72 $flagExternalEdit = isset($flags['ExternalEdit']);
74 $id = cleanid($id);
75 $file = wikiFN($id);
76 $created = @filectime($file);
77 $minor = ($type===DOKU_CHANGE_TYPE_MINOR_EDIT);
78 $wasRemoved = ($type===DOKU_CHANGE_TYPE_DELETE);
80 if(!$date) $date = time(); //use current time if none supplied
81 $remote = (!$flagExternalEdit)?clientIP(true):'127.0.0.1';
82 $user = (!$flagExternalEdit)?$INPUT->server->str('REMOTE_USER'):'';
83 if($sizechange === null) {
84 $sizechange = '';
85 } else {
86 $sizechange = (int) $sizechange;
89 $strip = array("\t", "\n");
90 $logline = array(
91 'date' => $date,
92 'ip' => $remote,
93 'type' => str_replace($strip, '', $type),
94 'id' => $id,
95 'user' => $user,
96 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
97 'extra' => str_replace($strip, '', $extra),
98 'sizechange' => $sizechange
101 $wasCreated = ($type===DOKU_CHANGE_TYPE_CREATE);
102 $wasReverted = ($type===DOKU_CHANGE_TYPE_REVERT);
103 // update metadata
104 if (!$wasRemoved) {
105 $oldmeta = p_read_metadata($id);
106 $meta = array();
107 if ($wasCreated && empty($oldmeta['persistent']['date']['created'])){
108 // newly created
109 $meta['date']['created'] = $created;
110 if ($user){
111 $meta['creator'] = $INFO['userinfo']['name'];
112 $meta['user'] = $user;
114 } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['persistent']['date']['created'])) {
115 // re-created / restored
116 $meta['date']['created'] = $oldmeta['persistent']['date']['created'];
117 $meta['date']['modified'] = $created; // use the files ctime here
118 $meta['creator'] = $oldmeta['persistent']['creator'];
119 if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name'];
120 } elseif (!$minor) { // non-minor modification
121 $meta['date']['modified'] = $date;
122 if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name'];
124 $meta['last_change'] = $logline;
125 p_set_metadata($id, $meta);
128 // add changelog lines
129 $logline = implode("\t", $logline)."\n";
130 io_saveFile(metaFN($id,'.changes'),$logline,true); //page changelog
131 io_saveFile($conf['changelog'],$logline,true); //global changelog cache
135 * Add's an entry to the media changelog
137 * @author Michael Hamann <michael@content-space.de>
138 * @author Andreas Gohr <andi@splitbrain.org>
139 * @author Esther Brunner <wikidesign@gmail.com>
140 * @author Ben Coburn <btcoburn@silicodon.net>
142 * @param int $date Timestamp of the change
143 * @param String $id Name of the affected page
144 * @param String $type Type of the change see DOKU_CHANGE_TYPE_*
145 * @param String $summary Summary of the change
146 * @param mixed $extra In case of a revert the revision (timestmp) of the reverted page
147 * @param array $flags Additional flags in a key value array.
148 * Available flags:
149 * - (none, so far)
150 * @param null|int $sizechange Change of filesize
152 function addMediaLogEntry(
153 $date,
154 $id,
155 $type=DOKU_CHANGE_TYPE_EDIT,
156 $summary='',
157 $extra='',
158 $flags=null,
159 $sizechange = null)
161 global $conf;
162 /** @var Input $INPUT */
163 global $INPUT;
165 $id = cleanid($id);
167 if(!$date) $date = time(); //use current time if none supplied
168 $remote = clientIP(true);
169 $user = $INPUT->server->str('REMOTE_USER');
170 if($sizechange === null) {
171 $sizechange = '';
172 } else {
173 $sizechange = (int) $sizechange;
176 $strip = array("\t", "\n");
177 $logline = array(
178 'date' => $date,
179 'ip' => $remote,
180 'type' => str_replace($strip, '', $type),
181 'id' => $id,
182 'user' => $user,
183 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
184 'extra' => str_replace($strip, '', $extra),
185 'sizechange' => $sizechange
188 // add changelog lines
189 $logline = implode("\t", $logline)."\n";
190 io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache
191 io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog
195 * returns an array of recently changed files using the
196 * changelog
198 * The following constants can be used to control which changes are
199 * included. Add them together as needed.
201 * RECENTS_SKIP_DELETED - don't include deleted pages
202 * RECENTS_SKIP_MINORS - don't include minor changes
203 * RECENTS_SKIP_SUBSPACES - don't include subspaces
204 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes
205 * RECENTS_MEDIA_PAGES_MIXED - return both media changes and page changes
207 * @param int $first number of first entry returned (for paginating
208 * @param int $num return $num entries
209 * @param string $ns restrict to given namespace
210 * @param int $flags see above
211 * @return array recently changed files
213 * @author Ben Coburn <btcoburn@silicodon.net>
214 * @author Kate Arzamastseva <pshns@ukr.net>
216 function getRecents($first,$num,$ns='',$flags=0){
217 global $conf;
218 $recent = array();
219 $count = 0;
221 if(!$num)
222 return $recent;
224 // read all recent changes. (kept short)
225 if ($flags & RECENTS_MEDIA_CHANGES) {
226 $lines = @file($conf['media_changelog']);
227 } else {
228 $lines = @file($conf['changelog']);
230 if (!is_array($lines)) {
231 $lines = array();
233 $lines_position = count($lines)-1;
234 $media_lines_position = 0;
235 $media_lines = array();
237 if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
238 $media_lines = @file($conf['media_changelog']);
239 if (!is_array($media_lines)) {
240 $media_lines = array();
242 $media_lines_position = count($media_lines)-1;
245 $seen = array(); // caches seen lines, _handleRecent() skips them
247 // handle lines
248 while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) {
249 if (empty($rec) && $lines_position >= 0) {
250 $rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen);
251 if (!$rec) {
252 $lines_position --;
253 continue;
256 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
257 $media_rec = _handleRecent(
258 @$media_lines[$media_lines_position],
259 $ns,
260 $flags | RECENTS_MEDIA_CHANGES,
261 $seen
263 if (!$media_rec) {
264 $media_lines_position --;
265 continue;
268 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
269 $media_lines_position--;
270 $x = $media_rec;
271 $x['media'] = true;
272 $media_rec = false;
273 } else {
274 $lines_position--;
275 $x = $rec;
276 if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
277 $rec = false;
279 if(--$first >= 0) continue; // skip first entries
280 $recent[] = $x;
281 $count++;
282 // break when we have enough entries
283 if($count >= $num){ break; }
285 return $recent;
289 * returns an array of files changed since a given time using the
290 * changelog
292 * The following constants can be used to control which changes are
293 * included. Add them together as needed.
295 * RECENTS_SKIP_DELETED - don't include deleted pages
296 * RECENTS_SKIP_MINORS - don't include minor changes
297 * RECENTS_SKIP_SUBSPACES - don't include subspaces
298 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes
300 * @param int $from date of the oldest entry to return
301 * @param int $to date of the newest entry to return (for pagination, optional)
302 * @param string $ns restrict to given namespace (optional)
303 * @param int $flags see above (optional)
304 * @return array of files
306 * @author Michael Hamann <michael@content-space.de>
307 * @author Ben Coburn <btcoburn@silicodon.net>
309 function getRecentsSince($from,$to=null,$ns='',$flags=0){
310 global $conf;
311 $recent = array();
313 if($to && $to < $from)
314 return $recent;
316 // read all recent changes. (kept short)
317 if ($flags & RECENTS_MEDIA_CHANGES) {
318 $lines = @file($conf['media_changelog']);
319 } else {
320 $lines = @file($conf['changelog']);
322 if(!$lines) return $recent;
324 // we start searching at the end of the list
325 $lines = array_reverse($lines);
327 // handle lines
328 $seen = array(); // caches seen lines, _handleRecent() skips them
330 foreach($lines as $line){
331 $rec = _handleRecent($line, $ns, $flags, $seen);
332 if($rec !== false) {
333 if ($rec['date'] >= $from) {
334 if (!$to || $rec['date'] <= $to) {
335 $recent[] = $rec;
337 } else {
338 break;
343 return array_reverse($recent);
347 * Internal function used by getRecents
349 * don't call directly
351 * @see getRecents()
352 * @author Andreas Gohr <andi@splitbrain.org>
353 * @author Ben Coburn <btcoburn@silicodon.net>
355 * @param string $line changelog line
356 * @param string $ns restrict to given namespace
357 * @param int $flags flags to control which changes are included
358 * @param array $seen listing of seen pages
359 * @return array|bool false or array with info about a change
361 function _handleRecent($line,$ns,$flags,&$seen){
362 if(empty($line)) return false; //skip empty lines
364 // split the line into parts
365 $recent = parseChangelogLine($line);
366 if ($recent===false) { return false; }
368 // skip seen ones
369 if(isset($seen[$recent['id']])) return false;
371 // skip minors
372 if($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
374 // remember in seen to skip additional sights
375 $seen[$recent['id']] = 1;
377 // check if it's a hidden page
378 if(isHiddenPage($recent['id'])) return false;
380 // filter namespace
381 if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false;
383 // exclude subnamespaces
384 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
386 // check ACL
387 if ($flags & RECENTS_MEDIA_CHANGES) {
388 $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
389 } else {
390 $recent['perms'] = auth_quickaclcheck($recent['id']);
392 if ($recent['perms'] < AUTH_READ) return false;
394 // check existance
395 if($flags & RECENTS_SKIP_DELETED){
396 $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
397 if(!file_exists($fn)) return false;
400 return $recent;