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 */ 8use dokuwiki\ChangeLog\MediaChangeLog; 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{ 22 return ChangeLog::parseLogLine($line); 23} 24 25/** 26 * Adds an entry to the changelog and saves the metadata for the page 27 * 28 * Note: timestamp of the change might not be unique especially after very quick 29 * repeated edits (e.g. change checkbox via do plugin) 30 * 31 * @param int $date Timestamp of the change 32 * @param String $id Name of the affected page 33 * @param String $type Type of the change see DOKU_CHANGE_TYPE_* 34 * @param String $summary Summary of the change 35 * @param mixed $extra In case of a revert the revision (timestamp) of the reverted page 36 * @param array $flags Additional flags in a key value array. 37 * Available flags: 38 * - ExternalEdit - mark as an external edit. 39 * @param null|int $sizechange Change of filesize 40 * 41 * @author Andreas Gohr <andi@splitbrain.org> 42 * @author Esther Brunner <wikidesign@gmail.com> 43 * @author Ben Coburn <btcoburn@silicodon.net> 44 * @deprecated 2021-11-28 45 */ 46function addLogEntry( 47 $date, 48 $id, 49 $type = DOKU_CHANGE_TYPE_EDIT, 50 $summary = '', 51 $extra = '', 52 $flags = null, 53 $sizechange = null 54) { 55 // no more used in DokuWiki core, but left for third-party plugins 56 dbg_deprecated('see '. PageFile::class .'::saveWikiText()'); 57 58 /** @var Input $INPUT */ 59 global $INPUT; 60 61 // check for special flags as keys 62 if (!is_array($flags)) $flags = []; 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) ? '127.0.0.1' : clientIP(true); 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 * Adds 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 (timestamp) 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 /** @var Input $INPUT */ 117 global $INPUT; 118 119 // check for special flags as keys 120 if (!is_array($flags)) $flags = []; 121 $flagExternalEdit = isset($flags['ExternalEdit']); 122 123 $id = cleanid($id); 124 125 if (!$date) $date = time(); //use current time if none supplied 126 $remote = ($flagExternalEdit) ? '127.0.0.1' : clientIP(true); 127 $user = ($flagExternalEdit) ? '' : $INPUT->server->str('REMOTE_USER'); 128 $sizechange = ($sizechange === null) ? '' : (int)$sizechange; 129 130 // update changelog file and get the added entry 131 (new MediaChangeLog($id, 1024))->addLogEntry([ 132 'date' => $date, 133 'ip' => $remote, 134 'type' => $type, 135 'id' => $id, 136 'user' => $user, 137 'sum' => $summary, 138 'extra' => $extra, 139 'sizechange' => $sizechange, 140 ]); 141} 142 143/** 144 * returns an array of recently changed files using the changelog 145 * 146 * The following constants can be used to control which changes are 147 * included. Add them together as needed. 148 * 149 * RECENTS_SKIP_DELETED - don't include deleted pages 150 * RECENTS_SKIP_MINORS - don't include minor changes 151 * RECENTS_ONLY_CREATION - only include new created pages and media 152 * RECENTS_SKIP_SUBSPACES - don't include subspaces 153 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 154 * RECENTS_MEDIA_PAGES_MIXED - return both media changes and page changes 155 * 156 * @param int $first number of first entry returned (for paginating 157 * @param int $num return $num entries 158 * @param string $ns restrict to given namespace 159 * @param int $flags see above 160 * @return array recently changed files 161 * 162 * @author Ben Coburn <btcoburn@silicodon.net> 163 * @author Kate Arzamastseva <pshns@ukr.net> 164 */ 165function getRecents($first, $num, $ns = '', $flags = 0) 166{ 167 global $conf; 168 $recent = []; 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 = []; 182 } 183 $lines_position = count($lines) - 1; 184 $media_lines_position = 0; 185 $media_lines = []; 186 187 if ($flags & RECENTS_MEDIA_PAGES_MIXED) { 188 $media_lines = @file($conf['media_changelog']) ?: []; 189 if (!is_array($media_lines)) { 190 $media_lines = []; 191 } 192 $media_lines_position = count($media_lines) - 1; 193 } 194 195 $seen = []; // 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){ 227 $x['media'] = true; 228 } else { 229 $x['media'] = false; 230 } 231 $rec = false; 232 } 233 if (--$first >= 0) continue; // skip first entries 234 $recent[] = $x; 235 $count++; 236 // break when we have enough entries 237 if ($count >= $num) { break; } 238 } 239 return $recent; 240} 241 242/** 243 * returns an array of files changed since a given time using the 244 * changelog 245 * 246 * The following constants can be used to control which changes are 247 * included. Add them together as needed. 248 * 249 * RECENTS_SKIP_DELETED - don't include deleted pages 250 * RECENTS_SKIP_MINORS - don't include minor changes 251 * RECENTS_ONLY_CREATION - only include new created pages and media 252 * RECENTS_SKIP_SUBSPACES - don't include subspaces 253 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 254 * 255 * @param int $from date of the oldest entry to return 256 * @param int $to date of the newest entry to return (for pagination, optional) 257 * @param string $ns restrict to given namespace (optional) 258 * @param int $flags see above (optional) 259 * @return array of files 260 * 261 * @author Michael Hamann <michael@content-space.de> 262 * @author Ben Coburn <btcoburn@silicodon.net> 263 */ 264function getRecentsSince($from, $to = null, $ns = '', $flags = 0) 265{ 266 global $conf; 267 $recent = []; 268 269 if ($to && $to < $from) 270 return $recent; 271 272 // read all recent changes. (kept short) 273 if ($flags & RECENTS_MEDIA_CHANGES) { 274 $lines = @file($conf['media_changelog']); 275 } else { 276 $lines = @file($conf['changelog']); 277 } 278 if (!$lines) return $recent; 279 280 // we start searching at the end of the list 281 $lines = array_reverse($lines); 282 283 // handle lines 284 $seen = []; // caches seen lines, _handleRecent() skips them 285 286 foreach ($lines as $line) { 287 $rec = _handleRecent($line, $ns, $flags, $seen); 288 if ($rec !== false) { 289 if ($rec['date'] >= $from) { 290 if (!$to || $rec['date'] <= $to) { 291 $recent[] = $rec; 292 } 293 } else { 294 break; 295 } 296 } 297 } 298 299 return array_reverse($recent); 300} 301 302/** 303 * Internal function used by getRecents 304 * 305 * don't call directly 306 * 307 * @see getRecents() 308 * @author Andreas Gohr <andi@splitbrain.org> 309 * @author Ben Coburn <btcoburn@silicodon.net> 310 * 311 * @param string $line changelog line 312 * @param string $ns restrict to given namespace 313 * @param int $flags flags to control which changes are included 314 * @param array $seen listing of seen pages 315 * @return array|bool false or array with info about a change 316 */ 317function _handleRecent($line, $ns, $flags, &$seen) 318{ 319 if (empty($line)) return false; //skip empty lines 320 321 // split the line into parts 322 $recent = ChangeLog::parseLogLine($line); 323 if ($recent === false) return false; 324 325 // skip seen ones 326 if (isset($seen[$recent['id']])) return false; 327 328 // skip changes, of only new items are requested 329 if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false; 330 331 // skip minors 332 if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false; 333 334 // remember in seen to skip additional sights 335 $seen[$recent['id']] = 1; 336 337 // check if it's a hidden page 338 if (isHiddenPage($recent['id'])) return false; 339 340 // filter namespace 341 if (($ns) && (strpos($recent['id'], $ns.':') !== 0)) return false; 342 343 // exclude subnamespaces 344 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false; 345 346 // check ACL 347 if ($flags & RECENTS_MEDIA_CHANGES) { 348 $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*'); 349 } else { 350 $recent['perms'] = auth_quickaclcheck($recent['id']); 351 } 352 if ($recent['perms'] < AUTH_READ) return false; 353 354 // check existence 355 if ($flags & RECENTS_SKIP_DELETED) { 356 $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id'])); 357 if (!file_exists($fn)) return false; 358 } 359 360 return $recent; 361} 362