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