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