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 9/** 10 * parses a changelog line into it's components 11 * 12 * @author Ben Coburn <btcoburn@silicodon.net> 13 * 14 * @param string $line changelog line 15 * @return array|bool parsed line or false 16 */ 17function 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; 33 } 34 return $info; 35 } else { 36 return false; 37 } 38} 39 40/** 41 * Adds an entry to the changelog and saves the metadata for the page 42 * 43 * @param int $date Timestamp of the change (after quick repeated edits not unique (e.g. change checkbox via do plugin) 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 52 * 53 * @author Andreas Gohr <andi@splitbrain.org> 54 * @author Esther Brunner <wikidesign@gmail.com> 55 * @author Ben Coburn <btcoburn@silicodon.net> 56 */ 57function 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; 61 62 // check for special flags as keys 63 if (!is_array($flags)) { $flags = array(); } 64 $flagExternalEdit = isset($flags['ExternalEdit']); 65 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); 71 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; 79 } 80 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 91 ); 92 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)['persistent']; 98 $meta = array(); 99 if ( 100 $wasCreated && ( 101 empty($oldmeta['date']['created']) || 102 $oldmeta['date']['created'] === $created 103 ) 104 ){ 105 // newly created 106 $meta['date']['created'] = $created; 107 if ($user){ 108 $meta['creator'] = isset($INFO) ? $INFO['userinfo']['name'] : null; 109 $meta['user'] = $user; 110 } 111 } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['date']['created'])) { 112 // re-created / restored 113 $meta['date']['created'] = $oldmeta['date']['created']; 114 $meta['date']['modified'] = $created; // use the files ctime here 115 $meta['creator'] = isset($oldmeta['creator']) ? $oldmeta['creator'] : null; 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; 120 } 121 $meta['last_change'] = $logline; 122 p_set_metadata($id, $meta); 123 } 124 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 129} 130 131/** 132 * Add's an entry to the media changelog 133 * 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> 138 * 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 148 */ 149function addMediaLogEntry( 150 $date, 151 $id, 152 $type=DOKU_CHANGE_TYPE_EDIT, 153 $summary='', 154 $extra='', 155 $flags=null, 156 $sizechange = null) 157{ 158 global $conf; 159 /** @var Input $INPUT */ 160 global $INPUT; 161 162 // check for special flags as keys 163 if (!is_array($flags)) $flags = array(); 164 $flagExternalEdit = isset($flags['ExternalEdit']); 165 166 $id = cleanid($id); 167 168 if (!$date) $date = time(); //use current time if none supplied 169 $remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1'; 170 $user = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : ''; 171 if ($sizechange === null) { 172 $sizechange = ''; 173 } else { 174 $sizechange = (int) $sizechange; 175 } 176 177 $strip = array("\t", "\n"); 178 $logline = array( 179 'date' => $date, 180 'ip' => $remote, 181 'type' => str_replace($strip, '', $type), 182 'id' => $id, 183 'user' => $user, 184 'sum' => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255), 185 'extra' => str_replace($strip, '', $extra), 186 'sizechange' => $sizechange 187 ); 188 189 // add changelog lines 190 $logline = implode("\t", $logline)."\n"; 191 io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache 192 io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog 193} 194 195/** 196 * returns an array of recently changed files using the 197 * changelog 198 * 199 * The following constants can be used to control which changes are 200 * included. Add them together as needed. 201 * 202 * RECENTS_SKIP_DELETED - don't include deleted pages 203 * RECENTS_SKIP_MINORS - don't include minor changes 204 * RECENTS_ONLY_CREATION - only include new created pages and media 205 * RECENTS_SKIP_SUBSPACES - don't include subspaces 206 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 207 * RECENTS_MEDIA_PAGES_MIXED - return both media changes and page changes 208 * 209 * @param int $first number of first entry returned (for paginating 210 * @param int $num return $num entries 211 * @param string $ns restrict to given namespace 212 * @param int $flags see above 213 * @return array recently changed files 214 * 215 * @author Ben Coburn <btcoburn@silicodon.net> 216 * @author Kate Arzamastseva <pshns@ukr.net> 217 */ 218function getRecents($first,$num,$ns='',$flags=0){ 219 global $conf; 220 $recent = array(); 221 $count = 0; 222 223 if(!$num) 224 return $recent; 225 226 // read all recent changes. (kept short) 227 if ($flags & RECENTS_MEDIA_CHANGES) { 228 $lines = @file($conf['media_changelog']) ?: []; 229 } else { 230 $lines = @file($conf['changelog']) ?: []; 231 } 232 if (!is_array($lines)) { 233 $lines = array(); 234 } 235 $lines_position = count($lines)-1; 236 $media_lines_position = 0; 237 $media_lines = array(); 238 239 if ($flags & RECENTS_MEDIA_PAGES_MIXED) { 240 $media_lines = @file($conf['media_changelog']) ?: []; 241 if (!is_array($media_lines)) { 242 $media_lines = array(); 243 } 244 $media_lines_position = count($media_lines)-1; 245 } 246 247 $seen = array(); // caches seen lines, _handleRecent() skips them 248 249 // handle lines 250 while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) { 251 if (empty($rec) && $lines_position >= 0) { 252 $rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen); 253 if (!$rec) { 254 $lines_position --; 255 continue; 256 } 257 } 258 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) { 259 $media_rec = _handleRecent( 260 @$media_lines[$media_lines_position], 261 $ns, 262 $flags | RECENTS_MEDIA_CHANGES, 263 $seen 264 ); 265 if (!$media_rec) { 266 $media_lines_position --; 267 continue; 268 } 269 } 270 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) { 271 $media_lines_position--; 272 $x = $media_rec; 273 $x['media'] = true; 274 $media_rec = false; 275 } else { 276 $lines_position--; 277 $x = $rec; 278 if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true; 279 $rec = false; 280 } 281 if(--$first >= 0) continue; // skip first entries 282 $recent[] = $x; 283 $count++; 284 // break when we have enough entries 285 if($count >= $num){ break; } 286 } 287 return $recent; 288} 289 290/** 291 * returns an array of files changed since a given time using the 292 * changelog 293 * 294 * The following constants can be used to control which changes are 295 * included. Add them together as needed. 296 * 297 * RECENTS_SKIP_DELETED - don't include deleted pages 298 * RECENTS_SKIP_MINORS - don't include minor changes 299 * RECENTS_ONLY_CREATION - only include new created pages and media 300 * RECENTS_SKIP_SUBSPACES - don't include subspaces 301 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 302 * 303 * @param int $from date of the oldest entry to return 304 * @param int $to date of the newest entry to return (for pagination, optional) 305 * @param string $ns restrict to given namespace (optional) 306 * @param int $flags see above (optional) 307 * @return array of files 308 * 309 * @author Michael Hamann <michael@content-space.de> 310 * @author Ben Coburn <btcoburn@silicodon.net> 311 */ 312function getRecentsSince($from,$to=null,$ns='',$flags=0){ 313 global $conf; 314 $recent = array(); 315 316 if($to && $to < $from) 317 return $recent; 318 319 // read all recent changes. (kept short) 320 if ($flags & RECENTS_MEDIA_CHANGES) { 321 $lines = @file($conf['media_changelog']); 322 } else { 323 $lines = @file($conf['changelog']); 324 } 325 if(!$lines) return $recent; 326 327 // we start searching at the end of the list 328 $lines = array_reverse($lines); 329 330 // handle lines 331 $seen = array(); // caches seen lines, _handleRecent() skips them 332 333 foreach($lines as $line){ 334 $rec = _handleRecent($line, $ns, $flags, $seen); 335 if($rec !== false) { 336 if ($rec['date'] >= $from) { 337 if (!$to || $rec['date'] <= $to) { 338 $recent[] = $rec; 339 } 340 } else { 341 break; 342 } 343 } 344 } 345 346 return array_reverse($recent); 347} 348 349/** 350 * Internal function used by getRecents 351 * 352 * don't call directly 353 * 354 * @see getRecents() 355 * @author Andreas Gohr <andi@splitbrain.org> 356 * @author Ben Coburn <btcoburn@silicodon.net> 357 * 358 * @param string $line changelog line 359 * @param string $ns restrict to given namespace 360 * @param int $flags flags to control which changes are included 361 * @param array $seen listing of seen pages 362 * @return array|bool false or array with info about a change 363 */ 364function _handleRecent($line,$ns,$flags,&$seen){ 365 if(empty($line)) return false; //skip empty lines 366 367 // split the line into parts 368 $recent = parseChangelogLine($line); 369 if ($recent===false) { return false; } 370 371 // skip seen ones 372 if(isset($seen[$recent['id']])) return false; 373 374 // skip changes, of only new items are requested 375 if($recent['type']!==DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false; 376 377 // skip minors 378 if($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false; 379 380 // remember in seen to skip additional sights 381 $seen[$recent['id']] = 1; 382 383 // check if it's a hidden page 384 if(isHiddenPage($recent['id'])) return false; 385 386 // filter namespace 387 if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false; 388 389 // exclude subnamespaces 390 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false; 391 392 // check ACL 393 if ($flags & RECENTS_MEDIA_CHANGES) { 394 $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*'); 395 } else { 396 $recent['perms'] = auth_quickaclcheck($recent['id']); 397 } 398 if ($recent['perms'] < AUTH_READ) return false; 399 400 // check existance 401 if($flags & RECENTS_SKIP_DELETED){ 402 $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id'])); 403 if(!file_exists($fn)) return false; 404 } 405 406 return $recent; 407} 408