xref: /dokuwiki/inc/changelog.php (revision 79a2d7845d5e5e48fe3be8f192717de9294aaba5)
17d559c7fSBen Coburn<?php
27d559c7fSBen Coburn/**
37d559c7fSBen Coburn * Changelog handling functions
47d559c7fSBen Coburn *
57d559c7fSBen Coburn * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
67d559c7fSBen Coburn * @author     Andreas Gohr <andi@splitbrain.org>
77d559c7fSBen Coburn */
87d559c7fSBen Coburn
91d11f1d3SSatoshi Saharause dokuwiki\ChangeLog\ChangeLog;
107fba736bSSatoshi Saharause dokuwiki\File\PageFile;
111d11f1d3SSatoshi Sahara
127d559c7fSBen Coburn/**
137d559c7fSBen Coburn * parses a changelog line into it's components
147d559c7fSBen Coburn *
157d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net>
164f1e2cb3SGerrit Uitslag *
174f1e2cb3SGerrit Uitslag * @param string $line changelog line
184f1e2cb3SGerrit Uitslag * @return array|bool parsed line or false
197d559c7fSBen Coburn */
207d559c7fSBen Coburnfunction parseChangelogLine($line) {
211d11f1d3SSatoshi Sahara    return ChangeLog::parseLogLine($line);
227d559c7fSBen Coburn}
237d559c7fSBen Coburn
247d559c7fSBen Coburn/**
2563f13cadSDamien Regad * Adds an entry to the changelog and saves the metadata for the page
267d559c7fSBen Coburn *
276527839fSSatoshi Sahara * Note: timestamp of the change might not be unique especially after very quick
286527839fSSatoshi Sahara *       repeated edits (e.g. change checkbox via do plugin)
296527839fSSatoshi Sahara *
306527839fSSatoshi Sahara * @param int    $date      Timestamp of the change
31a365baeeSDominik Eckelmann * @param String $id        Name of the affected page
32a365baeeSDominik Eckelmann * @param String $type      Type of the change see DOKU_CHANGE_TYPE_*
33a365baeeSDominik Eckelmann * @param String $summary   Summary of the change
34a365baeeSDominik Eckelmann * @param mixed  $extra     In case of a revert the revision (timestmp) of the reverted page
35a365baeeSDominik Eckelmann * @param array  $flags     Additional flags in a key value array.
364f1e2cb3SGerrit Uitslag *                             Available flags:
37a365baeeSDominik Eckelmann *                             - ExternalEdit - mark as an external edit.
38ac3ed4afSGerrit Uitslag * @param null|int $sizechange Change of filesize
39a365baeeSDominik Eckelmann *
407d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org>
417d559c7fSBen Coburn * @author Esther Brunner <wikidesign@gmail.com>
427d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net>
4369f9b481SSatoshi Sahara * @deprecated 2021-11-28
447d559c7fSBen Coburn */
455d9428a0SSatoshi Saharafunction addLogEntry(
465d9428a0SSatoshi Sahara    $date,
475d9428a0SSatoshi Sahara    $id,
485d9428a0SSatoshi Sahara    $type = DOKU_CHANGE_TYPE_EDIT,
495d9428a0SSatoshi Sahara    $summary = '',
505d9428a0SSatoshi Sahara    $extra = '',
515d9428a0SSatoshi Sahara    $flags = null,
525d9428a0SSatoshi Sahara    $sizechange = null)
535d9428a0SSatoshi Sahara{
5469f9b481SSatoshi Sahara    // no more used in DokuWiki core, but left for third-party plugins
55*79a2d784SGerrit Uitslag    dbg_deprecated('see '. PageFile::class .'::saveWikiText()');
5669f9b481SSatoshi Sahara
57585bf44eSChristopher Smith    /** @var Input $INPUT */
58585bf44eSChristopher Smith    global $INPUT;
597d559c7fSBen Coburn
605aa52fafSBen Coburn    // check for special flags as keys
61252acce3SSatoshi Sahara    if (!is_array($flags)) $flags = array();
625aa52fafSBen Coburn    $flagExternalEdit = isset($flags['ExternalEdit']);
635aa52fafSBen Coburn
647d559c7fSBen Coburn    $id = cleanid($id);
657d559c7fSBen Coburn
667d559c7fSBen Coburn    if (!$date) $date = time(); //use current time if none supplied
672f9daf16SAndreas Gohr    $remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1';
68585bf44eSChristopher Smith    $user   = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : '';
691d11f1d3SSatoshi Sahara    $sizechange = ($sizechange === null) ? '' : (int)$sizechange;
707d559c7fSBen Coburn
711d11f1d3SSatoshi Sahara    // update changelog file and get the added entry that is also to be stored in metadata
727fba736bSSatoshi Sahara    $pageFile = new PageFile($id);
737fba736bSSatoshi Sahara    $logEntry = $pageFile->changelog->addLogEntry([
747d559c7fSBen Coburn        'date'       => $date,
757d559c7fSBen Coburn        'ip'         => $remote,
76c7192766SSatoshi Sahara        'type'       => $type,
777d559c7fSBen Coburn        'id'         => $id,
787d559c7fSBen Coburn        'user'       => $user,
79c7192766SSatoshi Sahara        'sum'        => $summary,
80c7192766SSatoshi Sahara        'extra'      => $extra,
81c7192766SSatoshi Sahara        'sizechange' => $sizechange,
821d11f1d3SSatoshi Sahara    ]);
837d559c7fSBen Coburn
847d559c7fSBen Coburn    // update metadata
857fba736bSSatoshi Sahara    $pageFile->updateMetadata($logEntry);
867d559c7fSBen Coburn}
877d559c7fSBen Coburn
887d559c7fSBen Coburn/**
8999c8d7f2Smichael * Add's an entry to the media changelog
9099c8d7f2Smichael *
9199c8d7f2Smichael * @author Michael Hamann <michael@content-space.de>
9299c8d7f2Smichael * @author Andreas Gohr <andi@splitbrain.org>
9399c8d7f2Smichael * @author Esther Brunner <wikidesign@gmail.com>
9499c8d7f2Smichael * @author Ben Coburn <btcoburn@silicodon.net>
954f1e2cb3SGerrit Uitslag *
964f1e2cb3SGerrit Uitslag * @param int    $date      Timestamp of the change
974f1e2cb3SGerrit Uitslag * @param String $id        Name of the affected page
984f1e2cb3SGerrit Uitslag * @param String $type      Type of the change see DOKU_CHANGE_TYPE_*
994f1e2cb3SGerrit Uitslag * @param String $summary   Summary of the change
1004f1e2cb3SGerrit Uitslag * @param mixed  $extra     In case of a revert the revision (timestmp) of the reverted page
1014f1e2cb3SGerrit Uitslag * @param array  $flags     Additional flags in a key value array.
1024f1e2cb3SGerrit Uitslag *                             Available flags:
1034f1e2cb3SGerrit Uitslag *                             - (none, so far)
104ac3ed4afSGerrit Uitslag * @param null|int $sizechange Change of filesize
10599c8d7f2Smichael */
10664159a61SAndreas Gohrfunction addMediaLogEntry(
10764159a61SAndreas Gohr    $date,
10864159a61SAndreas Gohr    $id,
10964159a61SAndreas Gohr    $type = DOKU_CHANGE_TYPE_EDIT,
11064159a61SAndreas Gohr    $summary = '',
11164159a61SAndreas Gohr    $extra = '',
11264159a61SAndreas Gohr    $flags = null,
11364159a61SAndreas Gohr    $sizechange = null)
11464159a61SAndreas Gohr{
115585bf44eSChristopher Smith    /** @var Input $INPUT */
116585bf44eSChristopher Smith    global $INPUT;
11799c8d7f2Smichael
118facfe250SSatoshi Sahara    // check for special flags as keys
119facfe250SSatoshi Sahara    if (!is_array($flags)) $flags = array();
120facfe250SSatoshi Sahara    $flagExternalEdit = isset($flags['ExternalEdit']);
121facfe250SSatoshi Sahara
12299c8d7f2Smichael    $id = cleanid($id);
12399c8d7f2Smichael
12499c8d7f2Smichael    if (!$date) $date = time(); //use current time if none supplied
125facfe250SSatoshi Sahara    $remote = (!$flagExternalEdit) ? clientIP(true) : '127.0.0.1';
126facfe250SSatoshi Sahara    $user   = (!$flagExternalEdit) ? $INPUT->server->str('REMOTE_USER') : '';
1271d11f1d3SSatoshi Sahara    $sizechange = ($sizechange === null) ? '' : (int)$sizechange;
12899c8d7f2Smichael
1291d11f1d3SSatoshi Sahara    // update changelog file and get the added entry
130*79a2d784SGerrit Uitslag    (new \dokuwiki\ChangeLog\MediaChangeLog($id, 1024))->addLogEntry([
13199c8d7f2Smichael        'date'       => $date,
13299c8d7f2Smichael        'ip'         => $remote,
133c7192766SSatoshi Sahara        'type'       => $type,
13499c8d7f2Smichael        'id'         => $id,
13599c8d7f2Smichael        'user'       => $user,
136c7192766SSatoshi Sahara        'sum'        => $summary,
137c7192766SSatoshi Sahara        'extra'      => $extra,
138c7192766SSatoshi Sahara        'sizechange' => $sizechange,
1391d11f1d3SSatoshi Sahara    ]);
14099c8d7f2Smichael}
14199c8d7f2Smichael
14299c8d7f2Smichael/**
143252acce3SSatoshi Sahara * returns an array of recently changed files using the changelog
1447d559c7fSBen Coburn *
1457d559c7fSBen Coburn * The following constants can be used to control which changes are
1467d559c7fSBen Coburn * included. Add them together as needed.
1477d559c7fSBen Coburn *
1487d559c7fSBen Coburn * RECENTS_SKIP_DELETED   - don't include deleted pages
1497d559c7fSBen Coburn * RECENTS_SKIP_MINORS    - don't include minor changes
15008e9b52fSPhy * RECENTS_ONLY_CREATION  - only include new created pages and media
1517d559c7fSBen Coburn * RECENTS_SKIP_SUBSPACES - don't include subspaces
1520b926329SKate Arzamastseva * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
1530b926329SKate Arzamastseva * RECENTS_MEDIA_PAGES_MIXED  - return both media changes and page changes
1547d559c7fSBen Coburn *
1557d559c7fSBen Coburn * @param int    $first   number of first entry returned (for paginating
1567d559c7fSBen Coburn * @param int    $num     return $num entries
1577d559c7fSBen Coburn * @param string $ns      restrict to given namespace
15859f20ea3SMichael Hamann * @param int    $flags   see above
15959f20ea3SMichael Hamann * @return array recently changed files
1607d559c7fSBen Coburn *
1617d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net>
16229778747SKate Arzamastseva * @author Kate Arzamastseva <pshns@ukr.net>
1637d559c7fSBen Coburn */
1647d559c7fSBen Coburnfunction getRecents($first, $num, $ns = '', $flags = 0) {
1657d559c7fSBen Coburn    global $conf;
1667d559c7fSBen Coburn    $recent = array();
1677d559c7fSBen Coburn    $count  = 0;
1687d559c7fSBen Coburn
1697d559c7fSBen Coburn    if (!$num)
1707d559c7fSBen Coburn        return $recent;
1717d559c7fSBen Coburn
1727d559c7fSBen Coburn    // read all recent changes. (kept short)
1730b926329SKate Arzamastseva    if ($flags & RECENTS_MEDIA_CHANGES) {
1748e3e8693SAndreas Gohr        $lines = @file($conf['media_changelog']) ?: [];
17599c8d7f2Smichael    } else {
1768e3e8693SAndreas Gohr        $lines = @file($conf['changelog']) ?: [];
17799c8d7f2Smichael    }
1781b266025SPhy    if (!is_array($lines)) {
1791b266025SPhy        $lines = array();
1801b266025SPhy    }
18129778747SKate Arzamastseva    $lines_position = count($lines) - 1;
18259f20ea3SMichael Hamann    $media_lines_position = 0;
18359f20ea3SMichael Hamann    $media_lines = array();
18429778747SKate Arzamastseva
1850b926329SKate Arzamastseva    if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
1868e3e8693SAndreas Gohr        $media_lines = @file($conf['media_changelog']) ?: [];
1871b266025SPhy        if (!is_array($media_lines)) {
1881b266025SPhy            $media_lines = array();
1891b266025SPhy        }
19029778747SKate Arzamastseva        $media_lines_position = count($media_lines) - 1;
19129778747SKate Arzamastseva    }
19229778747SKate Arzamastseva
19329778747SKate Arzamastseva    $seen = array(); // caches seen lines, _handleRecent() skips them
1947d559c7fSBen Coburn
1957d559c7fSBen Coburn    // handle lines
1960b926329SKate Arzamastseva    while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >= 0)) {
19729778747SKate Arzamastseva        if (empty($rec) && $lines_position >= 0) {
1981d901ab2SAndreas Gohr            $rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen);
19929778747SKate Arzamastseva            if (!$rec) {
20029778747SKate Arzamastseva                $lines_position --;
20129778747SKate Arzamastseva                continue;
20229778747SKate Arzamastseva            }
20329778747SKate Arzamastseva        }
2040b926329SKate Arzamastseva        if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
20564159a61SAndreas Gohr            $media_rec = _handleRecent(
20664159a61SAndreas Gohr                @$media_lines[$media_lines_position],
20764159a61SAndreas Gohr                $ns,
20864159a61SAndreas Gohr                $flags | RECENTS_MEDIA_CHANGES,
20964159a61SAndreas Gohr                $seen
21064159a61SAndreas Gohr            );
21129778747SKate Arzamastseva            if (!$media_rec) {
21229778747SKate Arzamastseva                $media_lines_position --;
21329778747SKate Arzamastseva                continue;
21429778747SKate Arzamastseva            }
21529778747SKate Arzamastseva        }
2160b926329SKate Arzamastseva        if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
21729778747SKate Arzamastseva            $media_lines_position--;
21829778747SKate Arzamastseva            $x = $media_rec;
219b5941dfaSKate Arzamastseva            $x['media'] = true;
22029778747SKate Arzamastseva            $media_rec = false;
22129778747SKate Arzamastseva        } else {
22229778747SKate Arzamastseva            $lines_position--;
22329778747SKate Arzamastseva            $x = $rec;
224421ec38eSKate Arzamastseva            if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
22529778747SKate Arzamastseva            $rec = false;
22629778747SKate Arzamastseva        }
2277d559c7fSBen Coburn        if (--$first >= 0) continue; // skip first entries
22829778747SKate Arzamastseva        $recent[] = $x;
2297d559c7fSBen Coburn        $count++;
2307d559c7fSBen Coburn        // break when we have enough entries
2317d559c7fSBen Coburn        if ($count >= $num) { break; }
2327d559c7fSBen Coburn    }
2337d559c7fSBen Coburn    return $recent;
2347d559c7fSBen Coburn}
2357d559c7fSBen Coburn
2367d559c7fSBen Coburn/**
23799c8d7f2Smichael * returns an array of files changed since a given time using the
23899c8d7f2Smichael * changelog
23999c8d7f2Smichael *
24099c8d7f2Smichael * The following constants can be used to control which changes are
24199c8d7f2Smichael * included. Add them together as needed.
24299c8d7f2Smichael *
24399c8d7f2Smichael * RECENTS_SKIP_DELETED   - don't include deleted pages
24499c8d7f2Smichael * RECENTS_SKIP_MINORS    - don't include minor changes
24508e9b52fSPhy * RECENTS_ONLY_CREATION  - only include new created pages and media
24699c8d7f2Smichael * RECENTS_SKIP_SUBSPACES - don't include subspaces
2470b926329SKate Arzamastseva * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
24899c8d7f2Smichael *
24999c8d7f2Smichael * @param int    $from    date of the oldest entry to return
25099c8d7f2Smichael * @param int    $to      date of the newest entry to return (for pagination, optional)
25199c8d7f2Smichael * @param string $ns      restrict to given namespace (optional)
25259f20ea3SMichael Hamann * @param int    $flags   see above (optional)
25359f20ea3SMichael Hamann * @return array of files
25499c8d7f2Smichael *
25599c8d7f2Smichael * @author Michael Hamann <michael@content-space.de>
25699c8d7f2Smichael * @author Ben Coburn <btcoburn@silicodon.net>
25799c8d7f2Smichael */
25899c8d7f2Smichaelfunction getRecentsSince($from, $to = null, $ns = '', $flags = 0) {
25999c8d7f2Smichael    global $conf;
26099c8d7f2Smichael    $recent = array();
26199c8d7f2Smichael
26299c8d7f2Smichael    if ($to && $to < $from)
26399c8d7f2Smichael        return $recent;
26499c8d7f2Smichael
26599c8d7f2Smichael    // read all recent changes. (kept short)
2660b926329SKate Arzamastseva    if ($flags & RECENTS_MEDIA_CHANGES) {
26799c8d7f2Smichael        $lines = @file($conf['media_changelog']);
26899c8d7f2Smichael    } else {
26999c8d7f2Smichael        $lines = @file($conf['changelog']);
27099c8d7f2Smichael    }
271e920a0a1SAndreas Gohr    if (!$lines) return $recent;
27299c8d7f2Smichael
27399c8d7f2Smichael    // we start searching at the end of the list
27499c8d7f2Smichael    $lines = array_reverse($lines);
27599c8d7f2Smichael
27699c8d7f2Smichael    // handle lines
27799c8d7f2Smichael    $seen = array(); // caches seen lines, _handleRecent() skips them
27899c8d7f2Smichael
27999c8d7f2Smichael    foreach ($lines as $line) {
28099c8d7f2Smichael        $rec = _handleRecent($line, $ns, $flags, $seen);
28199c8d7f2Smichael        if ($rec !== false) {
28299c8d7f2Smichael            if ($rec['date'] >= $from) {
28399c8d7f2Smichael                if (!$to || $rec['date'] <= $to) {
28499c8d7f2Smichael                    $recent[] = $rec;
28599c8d7f2Smichael                }
28699c8d7f2Smichael            } else {
28799c8d7f2Smichael                break;
28899c8d7f2Smichael            }
28999c8d7f2Smichael        }
29099c8d7f2Smichael    }
29199c8d7f2Smichael
29299c8d7f2Smichael    return array_reverse($recent);
29399c8d7f2Smichael}
29499c8d7f2Smichael
29599c8d7f2Smichael/**
2967d559c7fSBen Coburn * Internal function used by getRecents
2977d559c7fSBen Coburn *
2987d559c7fSBen Coburn * don't call directly
2997d559c7fSBen Coburn *
3007d559c7fSBen Coburn * @see getRecents()
3017d559c7fSBen Coburn * @author Andreas Gohr <andi@splitbrain.org>
3027d559c7fSBen Coburn * @author Ben Coburn <btcoburn@silicodon.net>
3034f1e2cb3SGerrit Uitslag *
3044f1e2cb3SGerrit Uitslag * @param string $line   changelog line
3054f1e2cb3SGerrit Uitslag * @param string $ns     restrict to given namespace
3064f1e2cb3SGerrit Uitslag * @param int    $flags  flags to control which changes are included
3074f1e2cb3SGerrit Uitslag * @param array  $seen   listing of seen pages
3084f1e2cb3SGerrit Uitslag * @return array|bool    false or array with info about a change
3097d559c7fSBen Coburn */
31099c8d7f2Smichaelfunction _handleRecent($line, $ns, $flags, &$seen) {
3117d559c7fSBen Coburn    if (empty($line)) return false;   //skip empty lines
3127d559c7fSBen Coburn
3137d559c7fSBen Coburn    // split the line into parts
3141d11f1d3SSatoshi Sahara    $recent = ChangeLog::parseLogLine($line);
315252acce3SSatoshi Sahara    if ($recent === false) return false;
3167d559c7fSBen Coburn
3177d559c7fSBen Coburn    // skip seen ones
3187d559c7fSBen Coburn    if (isset($seen[$recent['id']])) return false;
3197d559c7fSBen Coburn
32008e9b52fSPhy    // skip changes, of only new items are requested
32108e9b52fSPhy    if ($recent['type'] !== DOKU_CHANGE_TYPE_CREATE && ($flags & RECENTS_ONLY_CREATION)) return false;
32268f43bcfSTero Kivinen
3237d559c7fSBen Coburn    // skip minors
324ebf1501fSBen Coburn    if ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
3257d559c7fSBen Coburn
3267d559c7fSBen Coburn    // remember in seen to skip additional sights
3277d559c7fSBen Coburn    $seen[$recent['id']] = 1;
3287d559c7fSBen Coburn
3297d559c7fSBen Coburn    // check if it's a hidden page
3307d559c7fSBen Coburn    if (isHiddenPage($recent['id'])) return false;
3317d559c7fSBen Coburn
3327d559c7fSBen Coburn    // filter namespace
3337d559c7fSBen Coburn    if (($ns) && (strpos($recent['id'], $ns.':') !== 0)) return false;
3347d559c7fSBen Coburn
3357d559c7fSBen Coburn    // exclude subnamespaces
3367d559c7fSBen Coburn    if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
3377d559c7fSBen Coburn
3387d559c7fSBen Coburn    // check ACL
33942025dfdSMichael Hamann    if ($flags & RECENTS_MEDIA_CHANGES) {
34042025dfdSMichael Hamann        $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
34142025dfdSMichael Hamann    } else {
34299c8d7f2Smichael        $recent['perms'] = auth_quickaclcheck($recent['id']);
34342025dfdSMichael Hamann    }
34499c8d7f2Smichael    if ($recent['perms'] < AUTH_READ) return false;
3457d559c7fSBen Coburn
3467d559c7fSBen Coburn    // check existance
3471d901ab2SAndreas Gohr    if ($flags & RECENTS_SKIP_DELETED) {
34842025dfdSMichael Hamann        $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
34979e79377SAndreas Gohr        if (!file_exists($fn)) return false;
3501d901ab2SAndreas Gohr    }
3517d559c7fSBen Coburn
3527d559c7fSBen Coburn    return $recent;
3537d559c7fSBen Coburn}
354