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