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