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