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