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