17d559c7fSBen Coburn<?php 2d4f83172SAndreas Gohr 37d559c7fSBen Coburn/** 47d559c7fSBen Coburn * Changelog handling functions 57d559c7fSBen Coburn * 67d559c7fSBen Coburn * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 77d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org> 87d559c7fSBen Coburn */ 9d4f83172SAndreas Gohr 1024870174SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 111d11f1d3SSatoshi Saharause dokuwiki\ChangeLog\ChangeLog; 12adf3f0adSGerrit Uitslaguse dokuwiki\ChangeLog\RevisionInfo; 137fba736bSSatoshi Saharause dokuwiki\File\PageFile; 141d11f1d3SSatoshi Sahara 157d559c7fSBen Coburn/** 167d559c7fSBen Coburn * parses a changelog line into it's components 177d559c7fSBen Coburn * 184f1e2cb3SGerrit Uitslag * @param string $line changelog line 194f1e2cb3SGerrit Uitslag * @return array|bool parsed line or false 2071951841SGerrit Uitslag * 2171951841SGerrit Uitslag * @author Ben Coburn <btcoburn@silicodon.net> 2271951841SGerrit Uitslag * 2371951841SGerrit Uitslag * @deprecated 2023-09-25 247d559c7fSBen Coburn */ 25d868eb89SAndreas Gohrfunction parseChangelogLine($line) 26d868eb89SAndreas Gohr{ 2771951841SGerrit Uitslag dbg_deprecated('see ' . ChangeLog::class . '::parseLogLine()'); 281d11f1d3SSatoshi Sahara return ChangeLog::parseLogLine($line); 297d559c7fSBen Coburn} 307d559c7fSBen Coburn 317d559c7fSBen Coburn/** 3263f13cadSDamien Regad * Adds an entry to the changelog and saves the metadata for the page 337d559c7fSBen Coburn * 346527839fSSatoshi Sahara * Note: timestamp of the change might not be unique especially after very quick 356527839fSSatoshi Sahara * repeated edits (e.g. change checkbox via do plugin) 366527839fSSatoshi Sahara * 376527839fSSatoshi Sahara * @param int $date Timestamp of the change 38a365baeeSDominik Eckelmann * @param String $id Name of the affected page 39a365baeeSDominik Eckelmann * @param String $type Type of the change see DOKU_CHANGE_TYPE_* 40a365baeeSDominik Eckelmann * @param String $summary Summary of the change 41eeda7adaSGerrit Uitslag * @param mixed $extra In case of a revert the revision (timestamp) of the reverted page 42a365baeeSDominik Eckelmann * @param array $flags Additional flags in a key value array. 434f1e2cb3SGerrit Uitslag * Available flags: 44a365baeeSDominik Eckelmann * - ExternalEdit - mark as an external edit. 45ac3ed4afSGerrit Uitslag * @param null|int $sizechange Change of filesize 46a365baeeSDominik Eckelmann * 477d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org> 487d559c7fSBen Coburn * @author Esther Brunner <wikidesign@gmail.com> 497d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 5069f9b481SSatoshi Sahara * @deprecated 2021-11-28 517d559c7fSBen Coburn */ 525d9428a0SSatoshi Saharafunction addLogEntry( 535d9428a0SSatoshi Sahara $date, 545d9428a0SSatoshi Sahara $id, 555d9428a0SSatoshi Sahara $type = DOKU_CHANGE_TYPE_EDIT, 565d9428a0SSatoshi Sahara $summary = '', 575d9428a0SSatoshi Sahara $extra = '', 585d9428a0SSatoshi Sahara $flags = null, 59d868eb89SAndreas Gohr $sizechange = null 60d868eb89SAndreas Gohr) { 6169f9b481SSatoshi Sahara // no more used in DokuWiki core, but left for third-party plugins 6279a2d784SGerrit Uitslag dbg_deprecated('see ' . PageFile::class . '::saveWikiText()'); 6369f9b481SSatoshi Sahara 64585bf44eSChristopher Smith /** @var Input $INPUT */ 65585bf44eSChristopher Smith global $INPUT; 667d559c7fSBen Coburn 675aa52fafSBen Coburn // check for special flags as keys 6824870174SAndreas Gohr if (!is_array($flags)) $flags = []; 695aa52fafSBen Coburn $flagExternalEdit = isset($flags['ExternalEdit']); 705aa52fafSBen Coburn 717d559c7fSBen Coburn $id = cleanid($id); 727d559c7fSBen Coburn 737d559c7fSBen Coburn if (!$date) $date = time(); //use current time if none supplied 7424870174SAndreas Gohr $remote = ($flagExternalEdit) ? '127.0.0.1' : clientIP(true); 7524870174SAndreas Gohr $user = ($flagExternalEdit) ? '' : $INPUT->server->str('REMOTE_USER'); 761d11f1d3SSatoshi Sahara $sizechange = ($sizechange === null) ? '' : (int)$sizechange; 777d559c7fSBen Coburn 781d11f1d3SSatoshi Sahara // update changelog file and get the added entry that is also to be stored in metadata 797fba736bSSatoshi Sahara $pageFile = new PageFile($id); 807fba736bSSatoshi Sahara $logEntry = $pageFile->changelog->addLogEntry([ 817d559c7fSBen Coburn 'date' => $date, 827d559c7fSBen Coburn 'ip' => $remote, 83c7192766SSatoshi Sahara 'type' => $type, 847d559c7fSBen Coburn 'id' => $id, 857d559c7fSBen Coburn 'user' => $user, 86c7192766SSatoshi Sahara 'sum' => $summary, 87c7192766SSatoshi Sahara 'extra' => $extra, 88c7192766SSatoshi Sahara 'sizechange' => $sizechange, 891d11f1d3SSatoshi Sahara ]); 907d559c7fSBen Coburn 917d559c7fSBen Coburn // update metadata 927fba736bSSatoshi Sahara $pageFile->updateMetadata($logEntry); 937d559c7fSBen Coburn} 947d559c7fSBen Coburn 957d559c7fSBen Coburn/** 96eeda7adaSGerrit Uitslag * Adds an entry to the media changelog 9799c8d7f2Smichael * 9899c8d7f2Smichael * @author Michael Hamann <michael@content-space.de> 9999c8d7f2Smichael * @author Andreas Gohr <andi@splitbrain.org> 10099c8d7f2Smichael * @author Esther Brunner <wikidesign@gmail.com> 10199c8d7f2Smichael * @author Ben Coburn <btcoburn@silicodon.net> 1024f1e2cb3SGerrit Uitslag * 1034f1e2cb3SGerrit Uitslag * @param int $date Timestamp of the change 1044f1e2cb3SGerrit Uitslag * @param String $id Name of the affected page 1054f1e2cb3SGerrit Uitslag * @param String $type Type of the change see DOKU_CHANGE_TYPE_* 1064f1e2cb3SGerrit Uitslag * @param String $summary Summary of the change 107eeda7adaSGerrit Uitslag * @param mixed $extra In case of a revert the revision (timestamp) of the reverted page 1084f1e2cb3SGerrit Uitslag * @param array $flags Additional flags in a key value array. 1094f1e2cb3SGerrit Uitslag * Available flags: 1104f1e2cb3SGerrit Uitslag * - (none, so far) 111ac3ed4afSGerrit Uitslag * @param null|int $sizechange Change of filesize 11299c8d7f2Smichael */ 11364159a61SAndreas Gohrfunction addMediaLogEntry( 11464159a61SAndreas Gohr $date, 11564159a61SAndreas Gohr $id, 11664159a61SAndreas Gohr $type = DOKU_CHANGE_TYPE_EDIT, 11764159a61SAndreas Gohr $summary = '', 11864159a61SAndreas Gohr $extra = '', 11964159a61SAndreas Gohr $flags = null, 120d868eb89SAndreas Gohr $sizechange = null 121d868eb89SAndreas Gohr) { 122585bf44eSChristopher Smith /** @var Input $INPUT */ 123585bf44eSChristopher Smith global $INPUT; 12499c8d7f2Smichael 125facfe250SSatoshi Sahara // check for special flags as keys 12624870174SAndreas Gohr if (!is_array($flags)) $flags = []; 127facfe250SSatoshi Sahara $flagExternalEdit = isset($flags['ExternalEdit']); 128facfe250SSatoshi Sahara 12999c8d7f2Smichael $id = cleanid($id); 13099c8d7f2Smichael 13199c8d7f2Smichael if (!$date) $date = time(); //use current time if none supplied 13224870174SAndreas Gohr $remote = ($flagExternalEdit) ? '127.0.0.1' : clientIP(true); 13324870174SAndreas Gohr $user = ($flagExternalEdit) ? '' : $INPUT->server->str('REMOTE_USER'); 1341d11f1d3SSatoshi Sahara $sizechange = ($sizechange === null) ? '' : (int)$sizechange; 13599c8d7f2Smichael 1361d11f1d3SSatoshi Sahara // update changelog file and get the added entry 13724870174SAndreas Gohr (new MediaChangeLog($id, 1024))->addLogEntry([ 13899c8d7f2Smichael 'date' => $date, 13999c8d7f2Smichael 'ip' => $remote, 140c7192766SSatoshi Sahara 'type' => $type, 14199c8d7f2Smichael 'id' => $id, 14299c8d7f2Smichael 'user' => $user, 143c7192766SSatoshi Sahara 'sum' => $summary, 144c7192766SSatoshi Sahara 'extra' => $extra, 145c7192766SSatoshi Sahara 'sizechange' => $sizechange, 1461d11f1d3SSatoshi Sahara ]); 14799c8d7f2Smichael} 14899c8d7f2Smichael 14999c8d7f2Smichael/** 150252acce3SSatoshi Sahara * returns an array of recently changed files using the changelog 1517d559c7fSBen Coburn * 1527d559c7fSBen Coburn * The following constants can be used to control which changes are 1537d559c7fSBen Coburn * included. Add them together as needed. 1547d559c7fSBen Coburn * 1557d559c7fSBen Coburn * RECENTS_SKIP_DELETED - don't include deleted pages 1567d559c7fSBen Coburn * RECENTS_SKIP_MINORS - don't include minor changes 15708e9b52fSPhy * RECENTS_ONLY_CREATION - only include new created pages and media 1587d559c7fSBen Coburn * RECENTS_SKIP_SUBSPACES - don't include subspaces 1590b926329SKate Arzamastseva * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 1600b926329SKate Arzamastseva * RECENTS_MEDIA_PAGES_MIXED - return both media changes and page changes 1617d559c7fSBen Coburn * 1627d559c7fSBen Coburn * @param int $first number of first entry returned (for paginating 1637d559c7fSBen Coburn * @param int $num return $num entries 1647d559c7fSBen Coburn * @param string $ns restrict to given namespace 16559f20ea3SMichael Hamann * @param int $flags see above 16659f20ea3SMichael Hamann * @return array recently changed files 1677d559c7fSBen Coburn * 1687d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 16929778747SKate Arzamastseva * @author Kate Arzamastseva <pshns@ukr.net> 1707d559c7fSBen Coburn */ 171d868eb89SAndreas Gohrfunction getRecents($first, $num, $ns = '', $flags = 0) 172d868eb89SAndreas Gohr{ 1737d559c7fSBen Coburn global $conf; 17424870174SAndreas Gohr $recent = []; 1757d559c7fSBen Coburn $count = 0; 1767d559c7fSBen Coburn 177*9349c09bSGerrit Uitslag if (!$num) { 1787d559c7fSBen Coburn return $recent; 179*9349c09bSGerrit Uitslag } 1807d559c7fSBen Coburn 1817d559c7fSBen Coburn // read all recent changes. (kept short) 1820b926329SKate Arzamastseva if ($flags & RECENTS_MEDIA_CHANGES) { 1838e3e8693SAndreas Gohr $lines = @file($conf['media_changelog']) ?: []; 18499c8d7f2Smichael } else { 1858e3e8693SAndreas Gohr $lines = @file($conf['changelog']) ?: []; 18699c8d7f2Smichael } 1871b266025SPhy if (!is_array($lines)) { 18824870174SAndreas Gohr $lines = []; 1891b266025SPhy } 19029778747SKate Arzamastseva $lines_position = count($lines) - 1; 19159f20ea3SMichael Hamann $media_lines_position = 0; 19224870174SAndreas Gohr $media_lines = []; 19329778747SKate Arzamastseva 1940b926329SKate Arzamastseva if ($flags & RECENTS_MEDIA_PAGES_MIXED) { 1958e3e8693SAndreas Gohr $media_lines = @file($conf['media_changelog']) ?: []; 1961b266025SPhy if (!is_array($media_lines)) { 19724870174SAndreas Gohr $media_lines = []; 1981b266025SPhy } 19929778747SKate Arzamastseva $media_lines_position = count($media_lines) - 1; 20029778747SKate Arzamastseva } 20129778747SKate Arzamastseva 202*9349c09bSGerrit Uitslag $seen = []; // caches seen lines, _handleRecentLogLine() skips them 2037d559c7fSBen Coburn 2047d559c7fSBen Coburn // handle lines 2050b926329SKate Arzamastseva while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >= 0)) { 20629778747SKate Arzamastseva if (empty($rec) && $lines_position >= 0) { 207*9349c09bSGerrit Uitslag $rec = _handleRecentLogLine(@$lines[$lines_position], $ns, $flags, $seen); 20829778747SKate Arzamastseva if (!$rec) { 20929778747SKate Arzamastseva $lines_position--; 21029778747SKate Arzamastseva continue; 21129778747SKate Arzamastseva } 21229778747SKate Arzamastseva } 2130b926329SKate Arzamastseva if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) { 214*9349c09bSGerrit Uitslag $media_rec = _handleRecentLogLine( 21564159a61SAndreas Gohr @$media_lines[$media_lines_position], 21664159a61SAndreas Gohr $ns, 21764159a61SAndreas Gohr $flags | RECENTS_MEDIA_CHANGES, 21864159a61SAndreas Gohr $seen 21964159a61SAndreas Gohr ); 22029778747SKate Arzamastseva if (!$media_rec) { 22129778747SKate Arzamastseva $media_lines_position--; 22229778747SKate Arzamastseva continue; 22329778747SKate Arzamastseva } 22429778747SKate Arzamastseva } 2250b926329SKate Arzamastseva if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) { 22629778747SKate Arzamastseva $media_lines_position--; 22729778747SKate Arzamastseva $x = $media_rec; 228adf3f0adSGerrit Uitslag $x['mode'] = RevisionInfo::MODE_MEDIA; 22929778747SKate Arzamastseva $media_rec = false; 23029778747SKate Arzamastseva } else { 23129778747SKate Arzamastseva $lines_position--; 23229778747SKate Arzamastseva $x = $rec; 23335bad86aSTherealperO if ($flags & RECENTS_MEDIA_CHANGES) { 234adf3f0adSGerrit Uitslag $x['mode'] = RevisionInfo::MODE_MEDIA; 23535bad86aSTherealperO } else { 236adf3f0adSGerrit Uitslag $x['mode'] = RevisionInfo::MODE_PAGE; 23735bad86aSTherealperO } 23829778747SKate Arzamastseva $rec = false; 23929778747SKate Arzamastseva } 2407d559c7fSBen Coburn if (--$first >= 0) continue; // skip first entries 24129778747SKate Arzamastseva $recent[] = $x; 2427d559c7fSBen Coburn $count++; 2437d559c7fSBen Coburn // break when we have enough entries 244177d6836SAndreas Gohr if ($count >= $num) { 245d4f83172SAndreas Gohr break; 246d4f83172SAndreas Gohr } 2477d559c7fSBen Coburn } 2487d559c7fSBen Coburn return $recent; 2497d559c7fSBen Coburn} 2507d559c7fSBen Coburn 2517d559c7fSBen Coburn/** 25299c8d7f2Smichael * returns an array of files changed since a given time using the 25399c8d7f2Smichael * changelog 25499c8d7f2Smichael * 25599c8d7f2Smichael * The following constants can be used to control which changes are 25699c8d7f2Smichael * included. Add them together as needed. 25799c8d7f2Smichael * 25899c8d7f2Smichael * RECENTS_SKIP_DELETED - don't include deleted pages 25999c8d7f2Smichael * RECENTS_SKIP_MINORS - don't include minor changes 26008e9b52fSPhy * RECENTS_ONLY_CREATION - only include new created pages and media 26199c8d7f2Smichael * RECENTS_SKIP_SUBSPACES - don't include subspaces 2620b926329SKate Arzamastseva * RECENTS_MEDIA_CHANGES - return media changes instead of page changes 26399c8d7f2Smichael * 26499c8d7f2Smichael * @param int $from date of the oldest entry to return 26599c8d7f2Smichael * @param int $to date of the newest entry to return (for pagination, optional) 26699c8d7f2Smichael * @param string $ns restrict to given namespace (optional) 26759f20ea3SMichael Hamann * @param int $flags see above (optional) 26859f20ea3SMichael Hamann * @return array of files 26999c8d7f2Smichael * 27099c8d7f2Smichael * @author Michael Hamann <michael@content-space.de> 27199c8d7f2Smichael * @author Ben Coburn <btcoburn@silicodon.net> 27299c8d7f2Smichael */ 273d868eb89SAndreas Gohrfunction getRecentsSince($from, $to = null, $ns = '', $flags = 0) 274d868eb89SAndreas Gohr{ 27599c8d7f2Smichael global $conf; 27624870174SAndreas Gohr $recent = []; 27799c8d7f2Smichael 278*9349c09bSGerrit Uitslag if ($to && $to < $from) { 27999c8d7f2Smichael return $recent; 280*9349c09bSGerrit Uitslag } 28199c8d7f2Smichael 28299c8d7f2Smichael // read all recent changes. (kept short) 2830b926329SKate Arzamastseva if ($flags & RECENTS_MEDIA_CHANGES) { 28499c8d7f2Smichael $lines = @file($conf['media_changelog']); 28599c8d7f2Smichael } else { 28699c8d7f2Smichael $lines = @file($conf['changelog']); 28799c8d7f2Smichael } 288e920a0a1SAndreas Gohr if (!$lines) return $recent; 28999c8d7f2Smichael 29099c8d7f2Smichael // we start searching at the end of the list 29199c8d7f2Smichael $lines = array_reverse($lines); 29299c8d7f2Smichael 29399c8d7f2Smichael // handle lines 294*9349c09bSGerrit Uitslag $seen = []; // caches seen lines, _handleRecentLogLine() skips them 29599c8d7f2Smichael 29699c8d7f2Smichael foreach ($lines as $line) { 297*9349c09bSGerrit Uitslag $rec = _handleRecentLogLine($line, $ns, $flags, $seen); 29899c8d7f2Smichael if ($rec !== false) { 29999c8d7f2Smichael if ($rec['date'] >= $from) { 30099c8d7f2Smichael if (!$to || $rec['date'] <= $to) { 30199c8d7f2Smichael $recent[] = $rec; 30299c8d7f2Smichael } 30399c8d7f2Smichael } else { 30499c8d7f2Smichael break; 30599c8d7f2Smichael } 30699c8d7f2Smichael } 30799c8d7f2Smichael } 30899c8d7f2Smichael 30999c8d7f2Smichael return array_reverse($recent); 31099c8d7f2Smichael} 31199c8d7f2Smichael 31299c8d7f2Smichael/** 3137d559c7fSBen Coburn * Internal function used by getRecents 314*9349c09bSGerrit Uitslag * Parse a line and checks whether it should be included 3157d559c7fSBen Coburn * 3167d559c7fSBen Coburn * don't call directly 3177d559c7fSBen Coburn * 3187d559c7fSBen Coburn * @see getRecents() 3197d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org> 3207d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net> 3214f1e2cb3SGerrit Uitslag * 3224f1e2cb3SGerrit Uitslag * @param string $line changelog line 3234f1e2cb3SGerrit Uitslag * @param string $ns restrict to given namespace 3244f1e2cb3SGerrit Uitslag * @param int $flags flags to control which changes are included 3254f1e2cb3SGerrit Uitslag * @param array $seen listing of seen pages 3264f1e2cb3SGerrit Uitslag * @return array|bool false or array with info about a change 3277d559c7fSBen Coburn */ 328*9349c09bSGerrit Uitslagfunction _handleRecentLogLine($line, $ns, $flags, &$seen) 329d868eb89SAndreas Gohr{ 3307d559c7fSBen Coburn if (empty($line)) return false; //skip empty lines 3317d559c7fSBen Coburn 3327d559c7fSBen Coburn // split the line into parts 3331d11f1d3SSatoshi Sahara $recent = ChangeLog::parseLogLine($line); 334252acce3SSatoshi Sahara if ($recent === false) return false; 3357d559c7fSBen Coburn 3367d559c7fSBen Coburn // skip seen ones 3377d559c7fSBen Coburn if (isset($seen[$recent['id']])) return false; 3387d559c7fSBen Coburn 33908e9b52fSPhy // skip changes, of only new items are requested 34008e9b52fSPhy if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false; 34168f43bcfSTero Kivinen 3427d559c7fSBen Coburn // skip minors 343ebf1501fSBen Coburn if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false; 3447d559c7fSBen Coburn 3457d559c7fSBen Coburn // remember in seen to skip additional sights 3467d559c7fSBen Coburn $seen[$recent['id']] = 1; 3477d559c7fSBen Coburn 3487d559c7fSBen Coburn // check if it's a hidden page 3497d559c7fSBen Coburn if (isHiddenPage($recent['id'])) return false; 3507d559c7fSBen Coburn 3517d559c7fSBen Coburn // filter namespace 3527d559c7fSBen Coburn if (($ns) && (strpos($recent['id'], $ns . ':') !== 0)) return false; 3537d559c7fSBen Coburn 3547d559c7fSBen Coburn // exclude subnamespaces 3557d559c7fSBen Coburn if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false; 3567d559c7fSBen Coburn 3577d559c7fSBen Coburn // check ACL 35842025dfdSMichael Hamann if ($flags & RECENTS_MEDIA_CHANGES) { 35942025dfdSMichael Hamann $recent['perms'] = auth_quickaclcheck(getNS($recent['id']) . ':*'); 36042025dfdSMichael Hamann } else { 36199c8d7f2Smichael $recent['perms'] = auth_quickaclcheck($recent['id']); 36242025dfdSMichael Hamann } 36399c8d7f2Smichael if ($recent['perms'] < AUTH_READ) return false; 3647d559c7fSBen Coburn 365eeda7adaSGerrit Uitslag // check existence 3661d901ab2SAndreas Gohr if ($flags & RECENTS_SKIP_DELETED) { 36742025dfdSMichael Hamann $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id'])); 36879e79377SAndreas Gohr if (!file_exists($fn)) return false; 3691d901ab2SAndreas Gohr } 3707d559c7fSBen Coburn 3717d559c7fSBen Coburn return $recent; 3727d559c7fSBen Coburn} 373