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