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