xref: /dokuwiki/inc/Ui/Revisions.php (revision bde2a644b7f10af4f4d8a7ca52281cd954538ea9)
1<?php
2
3namespace dokuwiki\Ui;
4
5use dokuwiki\ChangeLog\PageChangeLog;
6use dokuwiki\ChangeLog\MediaChangeLog;
7use dokuwiki\Form\Form;
8
9/**
10 * DokuWiki Revisions Interface
11 *
12 * @package dokuwiki\Ui
13 */
14class Revisions extends Ui
15{
16    protected $first;
17    protected $media_id;
18
19    /**
20     * Revisions Ui constructor
21     *
22     * @param int $first  skip the first n changelog lines
23     * @param bool|string $media_id  id of media, or false for current page
24     */
25    public function __construct($first = 0, $media_id = false)
26    {
27        $this->first    = $first;
28        $this->media_id = $media_id;
29    }
30
31    /**
32     * Display list of old revisions
33     *
34     * @author Andreas Gohr <andi@splitbrain.org>
35     * @author Ben Coburn <btcoburn@silicodon.net>
36     * @author Kate Arzamastseva <pshns@ukr.net>
37     * @author Satoshi Sahara <sahara.satoshi@gmail.com>
38     *
39     * @triggers HTMLFORM_REVISIONS_OUTPUT
40     * @return void
41     */
42    public function show()
43    {
44        global $ID;
45
46        if ($this->media_id) {
47            return $this->showMediaRevisions($this->media_id);
48        } else {
49            return $this->showPageRevisions($ID);
50        }
51    }
52
53    /**
54     * Display a list of Media Revisions in the MediaManager
55     *
56     * @param string $id  media id
57     * @return void
58     */
59    protected function showMediaRevisions($id)
60    {
61        global $lang;
62
63        // get revisions, and set correct pagenation parameters (first, hasNext)
64        $first   = $this->first;
65        $hasNext = false;
66        $revisions = $this->getRevisions($first, $hasNext);
67
68        // create the form
69        $form = new Form([
70                'id' => 'page__revisions', // must not be "media__revisions"
71                'action' => media_managerURL(['image' => $id], '&'),
72                'class'  => 'changes',
73        ]);
74        $form->setHiddenField('mediado', 'diff'); // required for media revisions
75        $form->addTagOpen('div')->addClass('no');
76
77        // start listing
78        $form->addTagOpen('ul');
79        foreach ($revisions as $info) {
80            $rev = $info['date'];
81            $class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : '';
82            $form->addTagOpen('li')->addClass($class);
83            $form->addTagOpen('div')->addClass('li');
84
85            if (isset($info['current'])) {
86               $form->addCheckbox('rev2[]')->val('current');
87            } elseif (file_exists(mediaFN($id, $rev))) {
88                $form->addCheckbox('rev2[]')->val($rev);
89            } else {
90                $form->addCheckbox('')->val($rev)->attr('disabled','disabled');
91            }
92            $form->addHTML(' ');
93
94            $objRevInfo = $this->getObjRevInfo($info);
95            $html = implode(' ', [
96                $objRevInfo->editDate(),          // edit date and time
97                $objRevInfo->difflink(),          // link to diffview icon
98                $objRevInfo->itemName(),          // name of page or media
99                '<div>',
100                $objRevInfo->editSummary(),       // edit summary
101                $objRevInfo->editor(),            // editor info
102                html_sizechange($info['sizechange']), // size change indicator
103                $objRevInfo->currentIndicator(),  // current indicator (only when k=1)
104                '</div>',
105            ]);
106            $form->addHTML($html);
107
108            $form->addTagClose('div');
109            $form->addTagClose('li');
110        }
111        $form->addTagClose('ul');  // end of revision list
112
113        // show button for diff view
114        $form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit');
115
116        $form->addTagClose('div'); // close div class=no
117
118        // print form that might be modified by HTMLFORM_REVISIONS_OUTPUT event handlers
119        print $form->toHTML('revisions');
120
121        // provide navigation for pagenated revision list (of pages and/or media files)
122        print $this->htmlNavigation($id, $first, $hasNext);
123    }
124
125    /**
126     * Display a list of Page Revisions
127     *
128     * @return void
129     */
130    protected function showPageRevisions($id)
131    {
132        global $lang;
133
134        // get revisions, and set correct pagenation parameters (first, hasNext)
135        $first   = $this->first;
136        $hasNext = false;
137        $revisions = $this->getRevisions($first, $hasNext);
138
139        // print intro
140        print p_locale_xhtml('revisions');
141
142        // create the form
143        $form = new Form([
144                'id' => 'page__revisions',
145                'class' => 'changes',
146        ]);
147        $form->addTagOpen('div')->addClass('no');
148
149        // start listing
150        $form->addTagOpen('ul');
151        foreach ($revisions as $info) {
152            $rev = $info['date'];
153            $class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : '';
154            $form->addTagOpen('li')->addClass($class);
155            $form->addTagOpen('div')->addClass('li');
156
157            if (page_exists($id, $rev)) {
158                $form->addCheckbox('rev2[]')->val($rev);
159            } else {
160                $form->addCheckbox('')->val($rev)->attr('disabled','disabled');
161            }
162            $form->addHTML(' ');
163
164            $objRevInfo = $this->getObjRevInfo($info);
165            $html = implode(' ', [
166                $objRevInfo->editDate(),          // edit date and time
167                $objRevInfo->difflink(),          // link to diffview icon
168                $objRevInfo->itemName(),          // name of page or media
169                $objRevInfo->editSummary(),       // edit summary
170                $objRevInfo->editor(),            // editor info
171                $objRevInfo->sizechange(),        // size change indicator
172                $objRevInfo->currentIndicator(),  // current indicator (only when k=1)
173            ]);
174            $form->addHTML($html);
175            $form->addTagClose('div');
176            $form->addTagClose('li');
177        }
178        $form->addTagClose('ul');  // end of revision list
179
180        // show button for diff view
181        $form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit');
182
183        $form->addTagClose('div'); // close div class=no
184
185        // print form that might be modified by HTMLFORM_REVISIONS_OUTPUT event handlers
186        print $form->toHTML('revisions');
187
188        // provide navigation for pagenated revision list (of pages and/or media files)
189        print $this->htmlNavigation($id, $first, $hasNext);
190    }
191
192
193    /**
194     * Get revisions, and set correct pagenation parameters (first, hasNext)
195     *
196     * @param int  $first
197     * @param bool $hasNext
198     * @return array  revisions to be shown in a pagenated list
199     */
200    protected function getRevisions(&$first, &$hasNext)
201    {
202        global $INFO, $conf;
203
204        if ($this->media_id) {
205            $changelog = new MediaChangeLog($this->media_id);
206        } else {
207            $changelog = new PageChangeLog($INFO['id']);
208        }
209
210        $revisions = [];
211
212        /* we need to get one additional log entry to be able to
213         * decide if this is the last page or is there another one.
214         * see also Ui\Recent::getRecents()
215         */
216        $revlist = $changelog->getRevisions($first, $conf['recent'] +1);
217        if (count($revlist) == 0 && $first != 0) {
218            $first = 0;
219            $revlist = $changelog->getRevisions($first, $conf['recent'] +1);
220        }
221        $exists = ($this->media_id) ? file_exists(mediaFN($this->media_id)) : $INFO['exists'];
222        if ($first === 0 && $exists) {
223            // add current page or media as revision[0]
224            if ($this->media_id) {
225                $rev = filemtime(fullpath(mediaFN($this->media_id)));
226                $revisions[] = $changelog->getRevisionInfo($rev) + array(
227                        'media' => true,
228                        'current' => true,
229                );
230            } else {
231                $revisions[] = array(
232                        'date' => $INFO['lastmod'],
233                        'ip'   => null,
234                        'type' => $INFO['meta']['last_change']['type'],
235                        'id'   => $INFO['id'],
236                        'user' => $INFO['editor'],
237                        'sum'  => $INFO['sum'],
238                        'extra' => null,
239                        'sizechange' => $INFO['meta']['last_change']['sizechange'],
240                        'current' => true,
241                );
242            }
243        }
244
245        // decide if this is the last page or is there another one
246        $hasNext = false;
247        if (count($revlist) > $conf['recent']) {
248            $hasNext = true;
249            array_pop($revlist); // remove one additional log entry
250        }
251
252        // append each revison info array to the revisions
253        foreach ($revlist as $rev) {
254            if ($this->media_id) {
255                $revisions[] = $changelog->getRevisionInfo($rev) + array('media' => true);
256            } else {
257                $revisions[] = $changelog->getRevisionInfo($rev);
258            }
259        }
260        return $revisions;
261    }
262
263    /**
264     * Navigation buttons for Pagenation (prev/next)
265     *
266     * @param string $id  page id or media id
267     * @param int  $first
268     * @param bool $hasNext
269     * @return array  html
270     */
271    protected function htmlNavigation($id, $first, $hasNext)
272    {
273        global $conf;
274
275        $html = '<div class="pagenav">';
276        $last = $first + $conf['recent'];
277        if ($first > 0) {
278            $first = max($first - $conf['recent'], 0);
279            $html.= '<div class="pagenav-prev">';
280            if ($this->media_id) {
281                $html.= html_btn('newer', $id, "p", media_managerURL(['first' => $first], '&', false, true));
282            } else {
283                $html.= html_btn('newer', $id, "p" ,['do' => 'revisions', 'first' => $first]);
284            }
285            $html.= '</div>';
286        }
287        if ($hasNext) {
288            $html.= '<div class="pagenav-next">';
289            if ($this->media_id) {
290                $html.= html_btn('older', $id, "n", media_managerURL(['first' => $last], '&', false, true));
291            } else {
292                $html.= html_btn('older', $id, "n", ['do' => 'revisions', 'first' => $last]);
293            }
294            $html.= '</div>';
295        }
296        $html.= '</div>';
297        return $html;
298    }
299
300    /**
301     * Returns instance of objRevInfo
302     *
303     * @param array $info  Revision info structure of a page or media file
304     * @return objRevInfo object (anonymous class)
305     */
306    protected function getObjRevInfo(array $info)
307    {
308        return new class ($info) // anonymous class (objRevInfo)
309        {
310            protected $info;
311
312            public function __construct(array $info)
313            {
314                $this->info = $info;
315            }
316
317            // current indicator
318            public function currentIndicator()
319            {
320                global $lang;
321                return ($this->info['current']) ? '('.$lang['current'].')' : '';
322            }
323
324            // edit date and time of the page or media file
325            public function editDate()
326            {
327                return '<span class="date">'. dformat($this->info['date']) .'</span>';
328            }
329
330            // edit summary
331            public function editSummary()
332            {
333                return '<span class="sum">'.' – '. hsc($this->info['sum']).'</span>';
334            }
335
336            // editor of the page or media file
337            public function editor()
338            {
339                // slightly different with display of Ui\Recent, i.e. external edit
340                global $lang;
341                $html = '<span class="user">';
342                if (!$this->info['user'] && !$this->info['ip']) {
343                    $html.= '('.$lang['external_edit'].')';
344                } elseif ($this->info['user']) {
345                    $html.= '<bdi>'. editorinfo($this->info['user']) .'</bdi>';
346                    if (auth_ismanager()) $html.= ' <bdo dir="ltr">('. $this->info['ip'] .')</bdo>';
347                } else {
348                    $html.= '<bdo dir="ltr">'. $this->info['ip'] .'</bdo>';
349                }
350                $html.= '</span>';
351                return $html;
352            }
353
354            // name of the page or media file
355            public function itemName()
356            {
357                // slightly different with display of Ui\Recent, i.e. revison may not exists
358                $id = $this->info['id'];
359                $rev = $this->info['date'];
360
361                if (isset($this->info['media'])) {
362                    // media file revision
363                    if (isset($this->info['current'])) {
364                        $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view'], '&');
365                        $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
366                    } elseif (file_exists(mediaFN($id, $rev))) {
367                        $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view', 'rev'=> $rev], '&');
368                        $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
369                    } else {
370                        $html = $id;
371                    }
372                    return $html;
373                } else {
374                    // page revision
375                    $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
376                    if (!$display_name) $display_name = $id;
377                    if ($this->info['current'] || page_exists($id, $rev)) {
378                        $href = wl($id, "rev=$rev", false, '&');
379                        $html = '<a href="'.$href.'" class="wikilink1">'.$display_name.'</a>';
380                    } else {
381                        $html = $display_name;
382                    }
383                    return $html;
384                }
385            }
386
387            // icon difflink
388            public function difflink()
389            {
390                global $lang;
391                $id = $this->info['id'];
392                $rev = $this->info['date'];
393
394                if (isset($this->info['media'])) {
395                    // media file revision
396                    if (isset($this->info['current']) || !file_exists(mediaFN($id, $rev))) {
397                        $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
398                    } else {
399                        $href = media_managerURL(['image'=> $id, 'rev'=> $rev, 'mediado'=>'diff'], '&');
400                        $html = '<a href="'.$href.'" class="diff_link">'
401                              . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
402                              . ' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />'
403                              . '</a> ';
404                    }
405                    return $html;
406                } else {
407                    // page revision
408                    if ($this->info['current'] || !page_exists($id, $rev)) {
409                        $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
410                    } else {
411                        $href = wl($id, "rev=$rev,do=diff", false, '&');
412                        $html = '<a href="'.$href.'" class="diff_link">'
413                              . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
414                              . ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />'
415                              . '</a>';
416                    }
417                    return $html;
418                }
419            }
420
421            // size change
422            public function sizeChange()
423            {
424                $class = 'sizechange';
425                $value = filesize_h(abs($this->info['sizechange']));
426                if ($this->info['sizechange'] > 0) {
427                    $class .= ' positive';
428                    $value = '+' . $value;
429                } elseif ($this->info['sizechange'] < 0) {
430                    $class .= ' negative';
431                    $value = '-' . $value;
432                } else {
433                    $value = '±' . $value;
434                }
435                return '<span class="'.$class.'">'.$value.'</span>';
436            }
437        }; // end of anonymous class (objRevInfo)
438    }
439
440}
441