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 181 // read all recent changes. (kept short) 182 if ($flags & RECENTS_MEDIA_CHANGES) { 183 $lines = @file($conf['media_changelog']) ?: []; 184 } else { 185 $lines = @file($conf['changelog']) ?: []; 186 } 187 if (!is_array($lines)) { 188 $lines = []; 189 } 190 $lines_position = count($lines) - 1; 191 $media_lines_position = 0; 192 $media_lines = []; 193 194 if ($flags & RECENTS_MEDIA_PAGES_MIXED) { 195 $media_lines = @file($conf['media_changelog']) ?: []; 196 if (!is_array($media_lines)) { 197 $media_lines = []; 198 } 199 $media_lines_position = count($media_lines) - 1; 200 } 201 202 $seen = []; // caches seen lines, _handleRecentLogLine() skips them 203 204 // handle lines 205 while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >= 0)) { 206 if (empty($rec) && $lines_position >= 0) { 207 $rec = _handleRecentLogLine(@$lines[$lines_position], $ns, $flags, $seen); 208 if (!$rec) { 209 $lines_position--; 210 continue; 211 } 212 } 213 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) { 214 $media_rec = _handleRecentLogLine( 215 @$media_lines[$media_lines_position], 216 $ns, 217 $flags | RECENTS_MEDIA_CHANGES, 218 $seen 219 ); 220 if (!$media_rec) { 221 $media_lines_position--; 222 continue; 223 } 224 } 225 if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) { 226 $media_lines_position--; 227 $x = $media_rec; 228 $x['mode'] = RevisionInfo::MODE_MEDIA; 229 $media_rec = false; 230 } else { 231 $lines_position--; 232 $x = $rec; 233 if ($flags & RECENTS_MEDIA_CHANGES) { 234 $x['mode'] = RevisionInfo::MODE_MEDIA; 235 } else { 236 $x['mode'] = RevisionInfo::MODE_PAGE; 237 } 238 $rec = false; 239 } 240 if (--$first >= 0) continue; // skip first entries 241 $recent[] = $x; 242 $count++; 243 // break when we have enough entries 244 if ($count >= $num) { 245 break; 246 } 247 } 248 return $recent; 249} 250 251/** 252 * returns an array of files changed since a given time using the 253 * changelog 254 * 255 * The following constants can be used to control which changes are 256 * included. Add them together as needed. 257 * 258 * RECENTS_SKIP_DELETED - don't include deleted pages 259 * RECENTS_SKIP_MINORS - don't include minor changes 260 * RECENTS_ONLY_CREATION - only include new created pages and media 261 * RECENTS_SKIP_SUBSPACES - don't include subspaces 262 * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 263 * 264 * @param int $from date of the oldest entry to return 265 * @param int $to date of the newest entry to return (for pagination, optional) 266 * @param string $ns restrict to given namespace (optional) 267 * @param int $flags see above (optional) 268 * @return array of files 269 * 270 * @author Michael Hamann <michael@content-space.de> 271 * @author Ben Coburn <btcoburn@silicodon.net> 272 */ 273function getRecentsSince($from, $to = null, $ns = '', $flags = 0) 274{ 275 global $conf; 276 $recent = []; 277 278 if ($to && $to < $from) { 279 return $recent; 280 } 281 282 // read all recent changes. (kept short) 283 if ($flags & RECENTS_MEDIA_CHANGES) { 284 $lines = @file($conf['media_changelog']); 285 } else { 286 $lines = @file($conf['changelog']); 287 } 288 if (!$lines) return $recent; 289 290 // we start searching at the end of the list 291 $lines = array_reverse($lines); 292 293 // handle lines 294 $seen = []; // caches seen lines, _handleRecentLogLine() skips them 295 296 foreach ($lines as $line) { 297 $rec = _handleRecentLogLine($line, $ns, $flags, $seen); 298 if ($rec !== false) { 299 if ($rec['date'] >= $from) { 300 if (!$to || $rec['date'] <= $to) { 301 $recent[] = $rec; 302 } 303 } else { 304 break; 305 } 306 } 307 } 308 309 return array_reverse($recent); 310} 311 312/** 313 * Internal function used by getRecents 314 * Parse a line and checks whether it should be included 315 * 316 * don't call directly 317 * 318 * @see getRecents() 319 * @author Andreas Gohr <andi@splitbrain.org> 320 * @author Ben Coburn <btcoburn@silicodon.net> 321 * 322 * @param string $line changelog line 323 * @param string $ns restrict to given namespace 324 * @param int $flags flags to control which changes are included 325 * @param array $seen listing of seen pages 326 * @return array|bool false or array with info about a change 327 */ 328function _handleRecentLogLine($line, $ns, $flags, &$seen) 329{ 330 if (empty($line)) return false; //skip empty lines 331 332 // split the line into parts 333 $recent = ChangeLog::parseLogLine($line); 334 if ($recent === false) return false; 335 336 // skip seen ones 337 if (isset($seen[$recent['id']])) return false; 338 339 // skip changes, of only new items are requested 340 if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false; 341 342 // skip minors 343 if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false; 344 345 // remember in seen to skip additional sights 346 $seen[$recent['id']] = 1; 347 348 // check if it's a hidden page 349 if (isHiddenPage($recent['id'])) return false; 350 351 // filter namespace 352 if (($ns) && (strpos($recent['id'], $ns . ':') !== 0)) return false; 353 354 // exclude subnamespaces 355 if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false; 356 357 // check ACL 358 if ($flags & RECENTS_MEDIA_CHANGES) { 359 $recent['perms'] = auth_quickaclcheck(getNS($recent['id']) . ':*'); 360 } else { 361 $recent['perms'] = auth_quickaclcheck($recent['id']); 362 } 363 if ($recent['perms'] < AUTH_READ) return false; 364 365 // check existence 366 if ($flags & RECENTS_SKIP_DELETED) { 367 $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id'])); 368 if (!file_exists($fn)) return false; 369 } 370 371 return $recent; 372} 373