xref: /dokuwiki/inc/File/PageFile.php (revision 90fb952c4c30c09c8446076ba05991c89a3f0b01)
1b24e9c4aSSatoshi Sahara<?php
2b24e9c4aSSatoshi Sahara
3b24e9c4aSSatoshi Saharanamespace dokuwiki\File;
4b24e9c4aSSatoshi Sahara
5b24e9c4aSSatoshi Saharause dokuwiki\Cache\CacheInstructions;
6b24e9c4aSSatoshi Saharause dokuwiki\ChangeLog\PageChangeLog;
7b24e9c4aSSatoshi Saharause dokuwiki\Extension\Event;
879a2d784SGerrit Uitslaguse dokuwiki\Input\Input;
9b24e9c4aSSatoshi Saharause dokuwiki\Logger;
1079a2d784SGerrit Uitslaguse RuntimeException;
11b24e9c4aSSatoshi Sahara
12b24e9c4aSSatoshi Sahara/**
13b24e9c4aSSatoshi Sahara * Class PageFile : handles wiki text file and its change management for specific page
14b24e9c4aSSatoshi Sahara */
15b24e9c4aSSatoshi Saharaclass PageFile
16b24e9c4aSSatoshi Sahara{
17b24e9c4aSSatoshi Sahara    protected $id;
18b24e9c4aSSatoshi Sahara
19b24e9c4aSSatoshi Sahara    /* @var PageChangeLog $changelog */
20b24e9c4aSSatoshi Sahara    public $changelog;
21b24e9c4aSSatoshi Sahara
22b24e9c4aSSatoshi Sahara    /* @var array $data  initial data when event COMMON_WIKIPAGE_SAVE triggered */
23b24e9c4aSSatoshi Sahara    protected $data;
24b24e9c4aSSatoshi Sahara
25b24e9c4aSSatoshi Sahara    /**
26b24e9c4aSSatoshi Sahara     * PageFile constructor.
27b24e9c4aSSatoshi Sahara     *
28b24e9c4aSSatoshi Sahara     * @param string $id
29b24e9c4aSSatoshi Sahara     */
30b24e9c4aSSatoshi Sahara    public function __construct($id)
31b24e9c4aSSatoshi Sahara    {
32b24e9c4aSSatoshi Sahara        $this->id = $id;
33b24e9c4aSSatoshi Sahara        $this->changelog = new PageChangeLog($this->id);
34b24e9c4aSSatoshi Sahara    }
35b24e9c4aSSatoshi Sahara
36b24e9c4aSSatoshi Sahara    /** @return string */
37b24e9c4aSSatoshi Sahara    public function getId()
38b24e9c4aSSatoshi Sahara    {
39b24e9c4aSSatoshi Sahara        return $this->id;
40b24e9c4aSSatoshi Sahara    }
41b24e9c4aSSatoshi Sahara
42b24e9c4aSSatoshi Sahara    /** @return string */
43b24e9c4aSSatoshi Sahara    public function getPath($rev = '')
44b24e9c4aSSatoshi Sahara    {
45b24e9c4aSSatoshi Sahara        return wikiFN($this->id, $rev);
46b24e9c4aSSatoshi Sahara    }
47b24e9c4aSSatoshi Sahara
48b24e9c4aSSatoshi Sahara    /**
49b24e9c4aSSatoshi Sahara     * Get raw WikiText of the page, considering change type at revision date
50b24e9c4aSSatoshi Sahara     * similar to function rawWiki($id, $rev = '')
51b24e9c4aSSatoshi Sahara     *
52b24e9c4aSSatoshi Sahara     * @param int|false $rev  timestamp when a revision of wikitext is desired
53b24e9c4aSSatoshi Sahara     * @return string
54b24e9c4aSSatoshi Sahara     */
55b24e9c4aSSatoshi Sahara    public function rawWikiText($rev = null)
56b24e9c4aSSatoshi Sahara    {
57b24e9c4aSSatoshi Sahara        if ($rev !== null) {
58b24e9c4aSSatoshi Sahara            $revInfo = $rev ? $this->changelog->getRevisionInfo($rev) : false;
59b24e9c4aSSatoshi Sahara            return (!$revInfo || $revInfo['type'] == DOKU_CHANGE_TYPE_DELETE)
60b24e9c4aSSatoshi Sahara                ? '' // attic stores complete last page version for a deleted page
61b24e9c4aSSatoshi Sahara                : io_readWikiPage($this->getPath($rev), $this->id, $rev); // retrieve from attic
62b24e9c4aSSatoshi Sahara        } else {
63b24e9c4aSSatoshi Sahara            return io_readWikiPage($this->getPath(), $this->id, '');
64b24e9c4aSSatoshi Sahara        }
65b24e9c4aSSatoshi Sahara    }
66b24e9c4aSSatoshi Sahara
67b24e9c4aSSatoshi Sahara    /**
68b24e9c4aSSatoshi Sahara     * Saves a wikitext by calling io_writeWikiPage.
69b24e9c4aSSatoshi Sahara     * Also directs changelog and attic updates.
70b24e9c4aSSatoshi Sahara     *
71b24e9c4aSSatoshi Sahara     * @author Andreas Gohr <andi@splitbrain.org>
72b24e9c4aSSatoshi Sahara     * @author Ben Coburn <btcoburn@silicodon.net>
73b24e9c4aSSatoshi Sahara     *
74b24e9c4aSSatoshi Sahara     * @param string $text     wikitext being saved
75b24e9c4aSSatoshi Sahara     * @param string $summary  summary of text update
76b24e9c4aSSatoshi Sahara     * @param bool   $minor    mark this saved version as minor update
7779a2d784SGerrit Uitslag     * @return array|void data of event COMMON_WIKIPAGE_SAVE
78b24e9c4aSSatoshi Sahara     */
79b24e9c4aSSatoshi Sahara    public function saveWikiText($text, $summary, $minor = false)
80b24e9c4aSSatoshi Sahara    {
81b24e9c4aSSatoshi Sahara        /* Note to developers:
82b24e9c4aSSatoshi Sahara           This code is subtle and delicate. Test the behavior of
83b24e9c4aSSatoshi Sahara           the attic and changelog with dokuwiki and external edits
84b24e9c4aSSatoshi Sahara           after any changes. External edits change the wiki page
85b24e9c4aSSatoshi Sahara           directly without using php or dokuwiki.
86b24e9c4aSSatoshi Sahara         */
87b24e9c4aSSatoshi Sahara        global $conf;
88b24e9c4aSSatoshi Sahara        global $lang;
89b24e9c4aSSatoshi Sahara        global $REV;
90b24e9c4aSSatoshi Sahara        /* @var Input $INPUT */
91b24e9c4aSSatoshi Sahara        global $INPUT;
92b24e9c4aSSatoshi Sahara
93b24e9c4aSSatoshi Sahara        // prevent recursive call
94b24e9c4aSSatoshi Sahara        if (isset($this->data)) return;
95b24e9c4aSSatoshi Sahara
96b24e9c4aSSatoshi Sahara        $pagefile = $this->getPath();
97b24e9c4aSSatoshi Sahara        $currentRevision = @filemtime($pagefile);       // int or false
98b24e9c4aSSatoshi Sahara        $currentContent = $this->rawWikiText();
99b24e9c4aSSatoshi Sahara        $currentSize = file_exists($pagefile) ? filesize($pagefile) : 0;
100b24e9c4aSSatoshi Sahara
101b24e9c4aSSatoshi Sahara        // prepare data for event COMMON_WIKIPAGE_SAVE
102445164b2SAndreas Gohr        $data = [
103b24e9c4aSSatoshi Sahara            'id'             => $this->id,// should not be altered by any handlers
104b24e9c4aSSatoshi Sahara            'file'           => $pagefile,// same above
105b24e9c4aSSatoshi Sahara            'changeType'     => null,// set prior to event, and confirm later
106b24e9c4aSSatoshi Sahara            'revertFrom'     => $REV,
107b24e9c4aSSatoshi Sahara            'oldRevision'    => $currentRevision,
108b24e9c4aSSatoshi Sahara            'oldContent'     => $currentContent,
109b24e9c4aSSatoshi Sahara            'newRevision'    => 0,// only available in the after hook
110b24e9c4aSSatoshi Sahara            'newContent'     => $text,
111b24e9c4aSSatoshi Sahara            'summary'        => $summary,
11279a2d784SGerrit Uitslag            'contentChanged' => ($text != $currentContent),// confirm later
113b24e9c4aSSatoshi Sahara            'changeInfo'     => '',// automatically determined by revertFrom
114445164b2SAndreas Gohr            'sizechange'     => strlen($text) - strlen($currentContent),
115445164b2SAndreas Gohr        ];
116b24e9c4aSSatoshi Sahara
117b24e9c4aSSatoshi Sahara        // determine tentatively change type and relevant elements of event data
118b24e9c4aSSatoshi Sahara        if ($data['revertFrom']) {
119b24e9c4aSSatoshi Sahara            // new text may differ from exact revert revision
120b24e9c4aSSatoshi Sahara            $data['changeType'] = DOKU_CHANGE_TYPE_REVERT;
121b24e9c4aSSatoshi Sahara            $data['changeInfo'] = $REV;
122b24e9c4aSSatoshi Sahara        } elseif (trim($data['newContent']) == '') {
123b24e9c4aSSatoshi Sahara            // empty or whitespace only content deletes
124b24e9c4aSSatoshi Sahara            $data['changeType'] = DOKU_CHANGE_TYPE_DELETE;
125b24e9c4aSSatoshi Sahara        } elseif (!file_exists($pagefile)) {
126b24e9c4aSSatoshi Sahara            $data['changeType'] = DOKU_CHANGE_TYPE_CREATE;
127b24e9c4aSSatoshi Sahara        } else {
128b24e9c4aSSatoshi Sahara            // minor edits allowable only for logged in users
129b24e9c4aSSatoshi Sahara            $is_minor_change = ($minor && $conf['useacl'] && $INPUT->server->str('REMOTE_USER'));
130b24e9c4aSSatoshi Sahara            $data['changeType'] = $is_minor_change
131b24e9c4aSSatoshi Sahara                ? DOKU_CHANGE_TYPE_MINOR_EDIT
132b24e9c4aSSatoshi Sahara                : DOKU_CHANGE_TYPE_EDIT;
133b24e9c4aSSatoshi Sahara        }
134b24e9c4aSSatoshi Sahara
135b24e9c4aSSatoshi Sahara        $this->data = $data;
13636454bb5SSatoshi Sahara        $data['page'] = $this; // allow event handlers to use this class methods
13736454bb5SSatoshi Sahara
138b24e9c4aSSatoshi Sahara        $event = new Event('COMMON_WIKIPAGE_SAVE', $data);
139b24e9c4aSSatoshi Sahara        if (!$event->advise_before()) return;
140b24e9c4aSSatoshi Sahara
141b24e9c4aSSatoshi Sahara        // if the content has not been changed, no save happens (plugins may override this)
142b24e9c4aSSatoshi Sahara        if (!$data['contentChanged']) return;
143b24e9c4aSSatoshi Sahara
144b24e9c4aSSatoshi Sahara        // Check whether the pagefile has modified during $event->advise_before()
145b24e9c4aSSatoshi Sahara        clearstatcache();
146b24e9c4aSSatoshi Sahara        $fileRev = @filemtime($pagefile);
147b24e9c4aSSatoshi Sahara        if ($fileRev === $currentRevision) {
148b24e9c4aSSatoshi Sahara            // pagefile has not touched by plugin's event handler
149b24e9c4aSSatoshi Sahara            // add a potential external edit entry to changelog and store it into attic
150b24e9c4aSSatoshi Sahara            $this->detectExternalEdit();
151b24e9c4aSSatoshi Sahara            $filesize_old = $currentSize;
152b24e9c4aSSatoshi Sahara        } else {
153b24e9c4aSSatoshi Sahara            // pagefile has modified by plugin's event handler, confirm sizechange
154b24e9c4aSSatoshi Sahara            $filesize_old = (
155b24e9c4aSSatoshi Sahara                $data['changeType'] == DOKU_CHANGE_TYPE_CREATE || (
156b24e9c4aSSatoshi Sahara                $data['changeType'] == DOKU_CHANGE_TYPE_REVERT && !file_exists($pagefile))
157b24e9c4aSSatoshi Sahara            ) ? 0 : filesize($pagefile);
158b24e9c4aSSatoshi Sahara        }
159b24e9c4aSSatoshi Sahara
160b24e9c4aSSatoshi Sahara        // make change to the current file
161b24e9c4aSSatoshi Sahara        if ($data['changeType'] == DOKU_CHANGE_TYPE_DELETE) {
162b24e9c4aSSatoshi Sahara            // nothing to do when the file has already deleted
163b24e9c4aSSatoshi Sahara            if (!file_exists($pagefile)) return;
164b24e9c4aSSatoshi Sahara            // autoset summary on deletion
165b24e9c4aSSatoshi Sahara            if (blank($data['summary'])) {
166b24e9c4aSSatoshi Sahara                $data['summary'] = $lang['deleted'];
167b24e9c4aSSatoshi Sahara            }
168b24e9c4aSSatoshi Sahara            // send "update" event with empty data, so plugins can react to page deletion
169445164b2SAndreas Gohr            $ioData = [[$pagefile, '', false], getNS($this->id), noNS($this->id), false];
170b24e9c4aSSatoshi Sahara            Event::createAndTrigger('IO_WIKIPAGE_WRITE', $ioData);
171b24e9c4aSSatoshi Sahara            // pre-save deleted revision
172b24e9c4aSSatoshi Sahara            @touch($pagefile);
173b24e9c4aSSatoshi Sahara            clearstatcache();
174b24e9c4aSSatoshi Sahara            $data['newRevision'] = $this->saveOldRevision();
175b24e9c4aSSatoshi Sahara            // remove empty file
176b24e9c4aSSatoshi Sahara            @unlink($pagefile);
177b24e9c4aSSatoshi Sahara            $filesize_new = 0;
178b24e9c4aSSatoshi Sahara            // don't remove old meta info as it should be saved, plugins can use
179b24e9c4aSSatoshi Sahara            // IO_WIKIPAGE_WRITE for removing their metadata...
180b24e9c4aSSatoshi Sahara            // purge non-persistant meta data
181b24e9c4aSSatoshi Sahara            p_purge_metadata($this->id);
182b24e9c4aSSatoshi Sahara            // remove empty namespaces
183b24e9c4aSSatoshi Sahara            io_sweepNS($this->id, 'datadir');
184b24e9c4aSSatoshi Sahara            io_sweepNS($this->id, 'mediadir');
185b24e9c4aSSatoshi Sahara        } else {
186b24e9c4aSSatoshi Sahara            // save file (namespace dir is created in io_writeWikiPage)
187b24e9c4aSSatoshi Sahara            io_writeWikiPage($pagefile, $data['newContent'], $this->id);
188b24e9c4aSSatoshi Sahara            // pre-save the revision, to keep the attic in sync
189b24e9c4aSSatoshi Sahara            $data['newRevision'] = $this->saveOldRevision();
190b24e9c4aSSatoshi Sahara            $filesize_new = filesize($pagefile);
191b24e9c4aSSatoshi Sahara        }
192b24e9c4aSSatoshi Sahara        $data['sizechange'] = $filesize_new - $filesize_old;
193b24e9c4aSSatoshi Sahara
194b24e9c4aSSatoshi Sahara        $event->advise_after();
195b24e9c4aSSatoshi Sahara
1967fba736bSSatoshi Sahara        unset($data['page']);
1977fba736bSSatoshi Sahara
198b24e9c4aSSatoshi Sahara        // adds an entry to the changelog and saves the metadata for the page
1997fba736bSSatoshi Sahara        $logEntry = $this->changelog->addLogEntry([
2007fba736bSSatoshi Sahara            'date'       => $data['newRevision'],
2017fba736bSSatoshi Sahara            'ip'         => clientIP(true),
2027fba736bSSatoshi Sahara            'type'       => $data['changeType'],
2037fba736bSSatoshi Sahara            'id'         => $this->id,
2047fba736bSSatoshi Sahara            'user'       => $INPUT->server->str('REMOTE_USER'),
2057fba736bSSatoshi Sahara            'sum'        => $data['summary'],
2067fba736bSSatoshi Sahara            'extra'      => $data['changeInfo'],
2077fba736bSSatoshi Sahara            'sizechange' => $data['sizechange'],
2087fba736bSSatoshi Sahara        ]);
2097fba736bSSatoshi Sahara        // update metadata
2107fba736bSSatoshi Sahara        $this->updateMetadata($logEntry);
211b24e9c4aSSatoshi Sahara
212b24e9c4aSSatoshi Sahara        // update the purgefile (timestamp of the last time anything within the wiki was changed)
213b24e9c4aSSatoshi Sahara        io_saveFile($conf['cachedir'] . '/purgefile', time());
214b24e9c4aSSatoshi Sahara
215b24e9c4aSSatoshi Sahara        return $data;
216b24e9c4aSSatoshi Sahara    }
217b24e9c4aSSatoshi Sahara
218b24e9c4aSSatoshi Sahara    /**
219b24e9c4aSSatoshi Sahara     * Checks if the current page version is newer than the last entry in the page's changelog.
220b24e9c4aSSatoshi Sahara     * If so, we assume it has been an external edit and we create an attic copy and add a proper
221b24e9c4aSSatoshi Sahara     * changelog line.
222b24e9c4aSSatoshi Sahara     *
223b24e9c4aSSatoshi Sahara     * This check is only executed when the page is about to be saved again from the wiki,
224b24e9c4aSSatoshi Sahara     * triggered in @see saveWikiText()
225b24e9c4aSSatoshi Sahara     */
226b24e9c4aSSatoshi Sahara    public function detectExternalEdit()
227b24e9c4aSSatoshi Sahara    {
228b24e9c4aSSatoshi Sahara        $revInfo = $this->changelog->getCurrentRevisionInfo();
229b24e9c4aSSatoshi Sahara
230b24e9c4aSSatoshi Sahara        // only interested in external revision
231b24e9c4aSSatoshi Sahara        if (empty($revInfo) || !array_key_exists('timestamp', $revInfo)) return;
232b24e9c4aSSatoshi Sahara
233b24e9c4aSSatoshi Sahara        if ($revInfo['type'] != DOKU_CHANGE_TYPE_DELETE && !$revInfo['timestamp']) {
234b24e9c4aSSatoshi Sahara            // file is older than last revision, that is erroneous/incorrect occurence.
235b24e9c4aSSatoshi Sahara            // try to change file modification time
236b24e9c4aSSatoshi Sahara            $fileLastMod = $this->getPath();
237b24e9c4aSSatoshi Sahara            $wrong_timestamp = filemtime($fileLastMod);
238b24e9c4aSSatoshi Sahara            if (touch($fileLastMod, $revInfo['date'])) {
239b24e9c4aSSatoshi Sahara                clearstatcache();
24079a2d784SGerrit Uitslag                $msg = "PageFile($this->id)::detectExternalEdit(): timestamp successfully modified";
241b24e9c4aSSatoshi Sahara                $details = '(' . $wrong_timestamp . ' -> ' . $revInfo['date'] . ')';
242b24e9c4aSSatoshi Sahara                Logger::error($msg, $details, $fileLastMod);
243b24e9c4aSSatoshi Sahara            } else {
244b24e9c4aSSatoshi Sahara                // runtime error
24579a2d784SGerrit Uitslag                $msg = "PageFile($this->id)::detectExternalEdit(): page file should be newer than last revision "
246b24e9c4aSSatoshi Sahara                      . '(' . filemtime($fileLastMod) . ' < ' . $this->changelog->lastRevision() . ')';
24779a2d784SGerrit Uitslag                throw new RuntimeException($msg);
248b24e9c4aSSatoshi Sahara            }
249b24e9c4aSSatoshi Sahara        }
250b24e9c4aSSatoshi Sahara
251b24e9c4aSSatoshi Sahara        // keep at least 1 sec before new page save
252b24e9c4aSSatoshi Sahara        if ($revInfo['date'] == time()) sleep(1); // wait a tick
253b24e9c4aSSatoshi Sahara
254b24e9c4aSSatoshi Sahara        // store externally edited file to the attic folder
255b24e9c4aSSatoshi Sahara        $this->saveOldRevision();
256b24e9c4aSSatoshi Sahara        // add a changelog entry for externally edited file
25779a2d784SGerrit Uitslag        $this->changelog->addLogEntry($revInfo);
258b24e9c4aSSatoshi Sahara        // remove soon to be stale instructions
259b24e9c4aSSatoshi Sahara        $cache = new CacheInstructions($this->id, $this->getPath());
260b24e9c4aSSatoshi Sahara        $cache->removeCache();
261b24e9c4aSSatoshi Sahara    }
262b24e9c4aSSatoshi Sahara
263b24e9c4aSSatoshi Sahara    /**
264b24e9c4aSSatoshi Sahara     * Moves the current version to the attic and returns its revision date
265b24e9c4aSSatoshi Sahara     *
266b24e9c4aSSatoshi Sahara     * @author Andreas Gohr <andi@splitbrain.org>
267b24e9c4aSSatoshi Sahara     *
268b24e9c4aSSatoshi Sahara     * @return int|string revision timestamp
269b24e9c4aSSatoshi Sahara     */
270b24e9c4aSSatoshi Sahara    public function saveOldRevision()
271b24e9c4aSSatoshi Sahara    {
272b24e9c4aSSatoshi Sahara        $oldfile = $this->getPath();
273b24e9c4aSSatoshi Sahara        if (!file_exists($oldfile)) return '';
274b24e9c4aSSatoshi Sahara        $date = filemtime($oldfile);
275b24e9c4aSSatoshi Sahara        $newfile = $this->getPath($date);
276b24e9c4aSSatoshi Sahara        io_writeWikiPage($newfile, $this->rawWikiText(), $this->id, $date);
277b24e9c4aSSatoshi Sahara        return $date;
278b24e9c4aSSatoshi Sahara    }
279b24e9c4aSSatoshi Sahara
2807fba736bSSatoshi Sahara    /**
2817fba736bSSatoshi Sahara     * Update metadata of changed page
2827fba736bSSatoshi Sahara     *
2837fba736bSSatoshi Sahara     * @param array $logEntry  changelog entry
2847fba736bSSatoshi Sahara     */
2857fba736bSSatoshi Sahara    public function updateMetadata(array $logEntry)
2867fba736bSSatoshi Sahara    {
2877fba736bSSatoshi Sahara        global $INFO;
2887fba736bSSatoshi Sahara
289445164b2SAndreas Gohr        ['date' => $date, 'type' => $changeType, 'user' => $user, ] = $logEntry;
2907fba736bSSatoshi Sahara
2917fba736bSSatoshi Sahara        $wasRemoved   = ($changeType === DOKU_CHANGE_TYPE_DELETE);
2927fba736bSSatoshi Sahara        $wasCreated   = ($changeType === DOKU_CHANGE_TYPE_CREATE);
2937fba736bSSatoshi Sahara        $wasReverted  = ($changeType === DOKU_CHANGE_TYPE_REVERT);
2947fba736bSSatoshi Sahara        $wasMinorEdit = ($changeType === DOKU_CHANGE_TYPE_MINOR_EDIT);
2957fba736bSSatoshi Sahara
29697b27cd4SSatoshi Sahara        $createdDate = @filectime($this->getPath());
2977fba736bSSatoshi Sahara
2987fba736bSSatoshi Sahara        if ($wasRemoved) return;
2997fba736bSSatoshi Sahara
3007fba736bSSatoshi Sahara        $oldmeta = p_read_metadata($this->id)['persistent'];
301445164b2SAndreas Gohr        $meta    = [];
3027fba736bSSatoshi Sahara
303*7d34963bSAndreas Gohr        if (
304*7d34963bSAndreas Gohr            $wasCreated &&
30597b27cd4SSatoshi Sahara            (empty($oldmeta['date']['created']) || $oldmeta['date']['created'] === $createdDate)
3067fba736bSSatoshi Sahara        ) {
3077fba736bSSatoshi Sahara            // newly created
30897b27cd4SSatoshi Sahara            $meta['date']['created'] = $createdDate;
3097fba736bSSatoshi Sahara            if ($user) {
31097b27cd4SSatoshi Sahara                $meta['creator'] = $INFO['userinfo']['name'] ?? null;
3117fba736bSSatoshi Sahara                $meta['user']    = $user;
3127fba736bSSatoshi Sahara            }
3137fba736bSSatoshi Sahara        } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['date']['created'])) {
3147fba736bSSatoshi Sahara            // re-created / restored
3157fba736bSSatoshi Sahara            $meta['date']['created']  = $oldmeta['date']['created'];
31697b27cd4SSatoshi Sahara            $meta['date']['modified'] = $createdDate; // use the files ctime here
31797b27cd4SSatoshi Sahara            $meta['creator'] = $oldmeta['creator'] ?? null;
3187fba736bSSatoshi Sahara            if ($user) {
31997b27cd4SSatoshi Sahara                $meta['contributor'][$user] = $INFO['userinfo']['name'] ?? null;
3207fba736bSSatoshi Sahara            }
3217fba736bSSatoshi Sahara        } elseif (!$wasMinorEdit) {   // non-minor modification
3227fba736bSSatoshi Sahara            $meta['date']['modified'] = $date;
3237fba736bSSatoshi Sahara            if ($user) {
32497b27cd4SSatoshi Sahara                $meta['contributor'][$user] = $INFO['userinfo']['name'] ?? null;
3257fba736bSSatoshi Sahara            }
3267fba736bSSatoshi Sahara        }
3277fba736bSSatoshi Sahara        $meta['last_change'] = $logEntry;
3287fba736bSSatoshi Sahara        p_set_metadata($this->id, $meta);
3297fba736bSSatoshi Sahara    }
330b24e9c4aSSatoshi Sahara}
331