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) { 238 break; } 239 } 240 return $recent; 241} 242 243/** 244 * returns an array of files changed since a given time using the 245 * changelog 246 * 247 * The following constants can be used to control which changes are 248 * included. Add them together as needed. 249 * 250 * RECENTS_SKIP_DELETED - don't include deleted pages 251 * RECENTS_SKIP_MINORS - don't include minor changes 252 * RECENTS_ONLY_CREATION - only include new created pages and media 253 * RECENTS_SKIP_SUBSPACES - don't include subspaces 254 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 255 * 256 * @param int $from date of the oldest entry to return 257 * @param int $to date of the newest entry to return (for pagination, optional) 258 * @param string $ns restrict to given namespace (optional) 259 * @param int $flags see above (optional) 260 * @return array of files 261 * 262 * @author Michael Hamann <michael@content-space.de> 263 * @author Ben Coburn <btcoburn@silicodon.net> 264 */ 265function getRecentsSince($from, $to = null, $ns = '', $flags = 0) 266{ 267 global $conf; 268 $recent = []; 269 270 if ($to && $to < $from) 271 return $recent; 272 273 // read all recent changes. (kept short) 274 if ($flags & RECENTS_MEDIA_CHANGES) { 275 $lines = @file($conf['media_changelog']); 276 } else { 277 $lines = @file($conf['changelog']); 278 } 279 if (!$lines) return $recent; 280 281 // we start searching at the end of the list 282 $lines = array_reverse($lines); 283 284 // handle lines 285 $seen = []; // caches seen lines, _handleRecent() skips them 286 287 foreach ($lines as $line) { 288 $rec = _handleRecent($line, $ns, $flags, $seen); 289 if ($rec !== false) { 290 if ($rec['date'] >= $from) { 291 if (!$to || $rec['date'] <= $to) { 292 $recent[] = $rec; 293 } 294 } else { 295 break; 296 } 297 } 298 } 299 300 return array_reverse($recent); 301} 302 303/** 304 * Internal function used by getRecents 305 * 306 * don't call directly 307 * 308 * @see getRecents() 309 * @author Andreas Gohr <andi@splitbrain.org> 310 * @author Ben Coburn <btcoburn@silicodon.net> 311 * 312 * @param string $line changelog line 313 * @param string $ns restrict to given namespace 314 * @param int $flags flags to control which changes are included 315 * @param array $seen listing of seen pages 316 * @return array|bool false or array with info about a change 317 */ 318function _handleRecent($line, $ns, $flags, &$seen) 319{ 320 if (empty($line)) return false; //skip empty lines 321 322 // split the line into parts 323 $recent = ChangeLog::parseLogLine($line); 324 if ($recent === false) return false; 325 326 // skip seen ones 327 if (isset($seen[$recent['id']])) return false; 328 329 // skip changes, of only new items are requested 330 if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false; 331 332 // skip minors 333 if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false; 334 335 // remember in seen to skip additional sights 336 $seen[$recent['id']] = 1; 337 338 // check if it's a hidden page 339 if (isHiddenPage($recent['id'])) return false; 340 341 // filter namespace 342 if (($ns) && (strpos($recent['id'], $ns . ':') !== 0)) return false; 343 344 // exclude subnamespaces 345 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false; 346 347 // check ACL 348 if ($flags & RECENTS_MEDIA_CHANGES) { 349 $recent['perms'] = auth_quickaclcheck(getNS($recent['id']) . ':*'); 350 } else { 351 $recent['perms'] = auth_quickaclcheck($recent['id']); 352 } 353 if ($recent['perms'] < AUTH_READ) return false; 354 355 // check existence 356 if ($flags & RECENTS_SKIP_DELETED) { 357 $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id'])); 358 if (!file_exists($fn)) return false; 359 } 360 361 return $recent; 362} 363