xref: /dokuwiki/inc/Ui/Recent.php (revision 79a2d7845d5e5e48fe3be8f192717de9294aaba5)
1<?php
2
3namespace dokuwiki\Ui;
4
5use dokuwiki\ChangeLog\PageChangeLog;
6use dokuwiki\ChangeLog\MediaChangeLog;
7use dokuwiki\Form\Form;
8
9/**
10 * DokuWiki Recent Interface
11 *
12 * @package dokuwiki\Ui
13 */
14class Recent extends Ui
15{
16    protected $first;
17    protected $show_changes;
18
19    /**
20     * Recent Ui constructor
21     *
22     * @param int $first  skip the first n changelog lines
23     * @param string $show_changes  type of changes to show; pages, mediafiles, or both
24     */
25    public function __construct($first = 0, $show_changes = 'both')
26    {
27        $this->first        = $first;
28        $this->show_changes = $show_changes;
29    }
30
31    /**
32     * Display recent changes
33     *
34     * @author Andreas Gohr <andi@splitbrain.org>
35     * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
36     * @author Ben Coburn <btcoburn@silicodon.net>
37     * @author Kate Arzamastseva <pshns@ukr.net>
38     * @author Satoshi Sahara <sahara.satoshi@gmail.com>
39     *
40     * @return void
41     */
42    public function show()
43    {
44        global $conf, $lang;
45        global $ID;
46
47        // get recent items, and set correct pagenation parameters (first, hasNext)
48        $first = $this->first;
49        $hasNext = false;
50        $recents = $this->getRecents($first, $hasNext);
51
52        // print intro
53        print p_locale_xhtml('recent');
54
55        if (getNS($ID) != '') {
56            print '<div class="level1"><p>'
57                . sprintf($lang['recent_global'], getNS($ID), wl('', 'do=recent'))
58                .'</p></div>';
59        }
60
61        // create the form
62        $form = new Form(['id'=>'dw__recent', 'method'=>'GET', 'action'=> wl($ID), 'class'=>'changes']);
63        $form->addTagOpen('div')->addClass('no');
64        $form->setHiddenField('sectok', null);
65        $form->setHiddenField('do', 'recent');
66        $form->setHiddenField('id', $ID);
67
68        // show dropdown selector, whether include not only recent pages but also recent media files?
69        if ($conf['mediarevisions']) {
70            $this->addRecentItemSelector($form);
71        }
72
73        // start listing of recent items
74        $form->addTagOpen('ul');
75        foreach ($recents as $recent) {
76            // check possible external edition for current page or media
77            $this->checkCurrentRevision($recent);
78            $recent['current'] = true;
79
80            $objRevInfo = $this->getObjRevInfo($recent);
81            $class = ($recent['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor': '';
82            $form->addTagOpen('li')->addClass($class);
83            $form->addTagOpen('div')->addClass('li');
84            $html = implode(' ', [
85                $objRevInfo->itemIcon(),          // filetype icon
86                $objRevInfo->editDate(),          // edit date and time
87                $objRevInfo->difflink(),          // link to diffview icon
88                $objRevInfo->revisionlink(),      // linkto revisions icon
89                $objRevInfo->itemName(),          // name of page or media
90                $objRevInfo->editSummary(),       // edit summary
91                $objRevInfo->editor(),            // editor info
92                $objRevInfo->sizechange(),        // size change indicator
93            ]);
94            $form->addHTML($html);
95            $form->addTagClose('div');
96            $form->addTagClose('li');
97        }
98        $form->addTagClose('ul');
99
100        $form->addTagClose('div'); // close div class=no
101
102        // provide navigation for pagenated recent list (of pages and/or media files)
103        $form->addHTML($this->htmlNavigation($first, $hasNext));
104
105        print $form->toHTML('Recent');
106    }
107
108    /**
109     * Get recent items, and set correct pagenation parameters (first, hasNext)
110     *
111     * @param int  $first
112     * @param bool $hasNext
113     * @return array  recent items to be shown in a pagenated list
114     *
115     * @see also dokuwiki\Changelog::getRevisionInfo()
116     */
117    protected function getRecents(&$first, &$hasNext)
118    {
119        global $ID, $conf;
120
121        $flags = 0;
122        if ($this->show_changes == 'mediafiles' && $conf['mediarevisions']) {
123            $flags = RECENTS_MEDIA_CHANGES;
124        } elseif ($this->show_changes == 'pages') {
125            $flags = 0;
126        } elseif ($conf['mediarevisions']) {
127            $flags = RECENTS_MEDIA_PAGES_MIXED;
128        }
129
130        /* we need to get one additionally log entry to be able to
131         * decide if this is the last page or is there another one.
132         * This is the cheapest solution to get this information.
133         */
134        $recents = getRecents($first, $conf['recent'] + 1, getNS($ID), $flags);
135        if (count($recents) == 0 && $first != 0) {
136            $first = 0;
137            $recents = getRecents($first, $conf['recent'] + 1, getNS($ID), $flags);
138        }
139
140        $hasNext = false;
141        if (count($recents) > $conf['recent']) {
142            $hasNext = true;
143            array_pop($recents); // remove extra log entry
144        }
145        return $recents;
146    }
147
148    /**
149     * Check possible external deletion for current page or media
150     *
151     * To keep sort order in the recent list, we ignore externally modification.
152     * It is not possible to know when external deletion had happened,
153     * $info['date'] is to be incremented 1 second when such deletion detected.
154     */
155    protected function checkCurrentRevision(array &$info)
156    {
157        $itemType = strrpos($info['id'], '.') ? 'media' : 'page';
158        if ($itemType == 'page') {
159            $changelog = new PageChangelog($info['id']);
160        } else {
161            $changelog = new MediaChangelog($info['id']);
162        }
163        if (!$changelog->isCurrentRevision($info['date'])) {
164            $currentRevInfo = $changelog->getCurrentRevisionInfo();
165            if ($currentRevInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
166                // the page or media file was externally deleted
167                $info = array_merge($info, $currentRevInfo);
168            }
169        }
170        unset($changelog);
171    }
172
173    /**
174     * Navigation buttons for Pagenation (prev/next)
175     *
176     * @param int  $first
177     * @param bool $hasNext
178     * @return string html
179     */
180    protected function htmlNavigation($first, $hasNext)
181    {
182        global $conf, $lang;
183
184        $last = $first + $conf['recent'];
185        $html = '<div class="pagenav">';
186        if ($first > 0) {
187            $first = max($first - $conf['recent'], 0);
188            $html.= '<div class="pagenav-prev">';
189            $html.= '<button type="submit" name="first['.$first.']" accesskey="n"'
190                  . ' title="'.$lang['btn_newer'].' [N]" class="button show">'
191                  . $lang['btn_newer']
192                  . '</button>';
193            $html.= '</div>';
194        }
195        if ($hasNext) {
196            $html.= '<div class="pagenav-next">';
197            $html.= '<button type="submit" name="first['.$last.']" accesskey="p"'
198                  . ' title="'.$lang['btn_older'].' [P]" class="button show">'
199                  . $lang['btn_older']
200                  . '</button>';
201            $html.= '</div>';
202        }
203        $html.= '</div>';
204        return $html;
205    }
206
207    /**
208     * Add dropdown selector of item types to the form instance
209     *
210     * @param Form $form
211     * @return void
212     */
213    protected function addRecentItemSelector(Form $form)
214    {
215        global $lang;
216
217        $form->addTagOpen('div')->addClass('changeType');
218        $options = array(
219                    'pages'      => $lang['pages_changes'],
220                    'mediafiles' => $lang['media_changes'],
221                    'both'       => $lang['both_changes'],
222        );
223        $form->addDropdown('show_changes', $options, $lang['changes_type'])
224                ->val($this->show_changes)->addClass('quickselect');
225        $form->addButton('do[recent]', $lang['btn_apply'])->attr('type','submit');
226        $form->addTagClose('div');
227    }
228
229    /**
230     * Returns instance of objRevInfo
231     * @param array $info  Revision info structure of page or media
232     * @return objRevInfo object (anonymous class)
233     */
234    protected function getObjRevInfo(array $info)
235    {
236        return new class ($info) // anonymous class (objRevInfo)
237        {
238            protected $info;
239
240            public function __construct(array $info)
241            {
242                $info['item'] = strrpos($info['id'], '.') ? 'media' : 'page';
243                $info['current'] = $info['current'] ?? false;
244                $this->info = $info;
245            }
246
247            // fileicon of the page or media file
248            public function itemIcon()
249            {
250                $id = $this->info['id'];
251                switch ($this->info['item']) {
252                    case 'media': // media file revision
253                        $html = media_printicon($id);
254                        break;
255                    case 'page': // page revision
256                        $html = '<img class="icon" src="'.DOKU_BASE.'lib/images/fileicons/file.png" alt="'.$id.'" />';
257                }
258                return $html;
259            }
260
261            // edit date and time of the page or media file
262            public function editDate()
263            {
264                return '<span class="date">'. dformat($this->info['date']) .'</span>';
265            }
266
267            // edit summary
268            public function editSummary()
269            {
270                return '<span class="sum">'.' – '. hsc($this->info['sum']).'</span>';
271            }
272
273            // editor of the page or media file
274            public function editor()
275            {
276                $html = '<span class="user">';
277                if ($this->info['user']) {
278                    $html.= '<bdi>'. editorinfo($this->info['user']) .'</bdi>';
279                    if (auth_ismanager()) $html.= ' <bdo dir="ltr">('. $this->info['ip'] .')</bdo>';
280                } else {
281                    $html.= '<bdo dir="ltr">'. $this->info['ip'] .'</bdo>';
282                }
283                $html.= '</span>';
284                return $html;
285            }
286
287            // name of the page or media file
288            public function itemName()
289            {
290                $id = $this->info['id'];
291                switch ($this->info['item']) {
292                    case 'media': // media file revision
293                        $href = media_managerURL(['tab_details'=>'view', 'image'=> $id, 'ns'=> getNS($id)], '&');
294                        $class = file_exists(mediaFN($id)) ? 'wikilink1' : 'wikilink2';
295                        $html = '<a href="'.$href.'" class="'.$class.'">'.$id.'</a>';
296                        return $html;
297                    case 'page': // page revision
298                        $html = html_wikilink(':'.$id, (useHeading('navigation') ? null : $id));
299                        return $html;
300                }
301                return '';
302            }
303
304            // icon difflink
305            public function difflink()
306            {
307                global $lang;
308                $id = $this->info['id'];
309
310                switch ($this->info['item']) {
311                    case 'media': // media file revision
312                        $revs = (new MediaChangeLog($id))->getRevisions(0, 1);
313                        $diff = (count($revs) && file_exists(mediaFN($id)));
314                        if ($diff) {
315                            $href = media_managerURL(
316                                ['tab_details'=>'history', 'mediado'=>'diff', 'image'=> $id, 'ns'=> getNS($id)], '&'
317                            );
318                        } else {
319                            $href = '';
320                        }
321                        break;
322                    case 'page': // page revision
323                        $href = wl($id, "do=diff", false, '&');
324                }
325
326                if ($href) {
327                    $html = '<a href="'.$href.'" class="diff_link">'
328                          . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
329                          . ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />'
330                          . '</a>';
331                } else {
332                    $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
333                }
334                return $html;
335            }
336
337            // icon revision link
338            public function revisionlink()
339            {
340                global $lang;
341                $id = $this->info['id'];
342                switch ($this->info['item']) {
343                    case 'media': // media file revision
344                        $href = media_managerURL(['tab_details'=>'history', 'image'=> $id, 'ns'=> getNS($id)], '&');
345                        break;
346                    case 'page': // page revision
347                        $href = wl($id, "do=revisions", false, '&');
348                }
349                $html = '<a href="'.$href.'" class="revisions_link">'
350                      . '<img src="'.DOKU_BASE.'lib/images/history.png" width="12" height="14"'
351                      . ' title="'.$lang['btn_revs'].'" alt="'.$lang['btn_revs'].'" />'
352                      . '</a>';
353                return $html;
354            }
355
356            // size change
357            public function sizeChange()
358            {
359                $class = 'sizechange';
360                $value = filesize_h(abs($this->info['sizechange']));
361                if ($this->info['sizechange'] > 0) {
362                    $class .= ' positive';
363                    $value = '+' . $value;
364                } elseif ($this->info['sizechange'] < 0) {
365                    $class .= ' negative';
366                    $value = '-' . $value;
367                } else {
368                    $value = '±' . $value;
369                }
370                return '<span class="'.$class.'">'.$value.'</span>';
371            }
372        }; // end of anonymous class (objRevInfo)
373    }
374
375}
376