1<?php
2
3namespace dokuwiki\Ui;
4
5use dokuwiki\ChangeLog\PageChangeLog;
6use dokuwiki\ChangeLog\MediaChangeLog;
7use dokuwiki\ChangeLog\RevisionInfo;
8use dokuwiki\Form\Form;
9
10/**
11 * DokuWiki Recent Interface
12 *
13 * @package dokuwiki\Ui
14 */
15class Recent extends Ui
16{
17    protected $first;
18    protected $show_changes;
19
20    /**
21     * Recent Ui constructor
22     *
23     * @param int $first  skip the first n changelog lines
24     * @param string $show_changes  type of changes to show; 'pages', 'mediafiles', or 'both'
25     */
26    public function __construct($first = 0, $show_changes = 'both')
27    {
28        $this->first        = $first;
29        $this->show_changes = $show_changes;
30    }
31
32    /**
33     * Display recent changes
34     *
35     * @author Andreas Gohr <andi@splitbrain.org>
36     * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
37     * @author Ben Coburn <btcoburn@silicodon.net>
38     * @author Kate Arzamastseva <pshns@ukr.net>
39     * @author Satoshi Sahara <sahara.satoshi@gmail.com>
40     *
41     * @return void
42     */
43    public function show()
44    {
45        global $conf, $lang;
46        global $ID;
47
48        // get recent items, and set correct pagination parameters (first, hasNext)
49        $first = $this->first;
50        $hasNext = false;
51        $recents = $this->getRecents($first, $hasNext);
52
53        // print intro
54        print p_locale_xhtml('recent');
55
56        if (getNS($ID) != '') {
57            print '<div class="level1"><p>'
58                . sprintf($lang['recent_global'], getNS($ID), wl('', 'do=recent'))
59                .'</p></div>';
60        }
61
62        // create the form
63        $form = new Form(['id'=>'dw__recent', 'method'=>'GET', 'action'=> wl($ID), 'class'=>'changes']);
64        $form->addTagOpen('div')->addClass('no');
65        $form->setHiddenField('sectok', null);
66        $form->setHiddenField('do', 'recent');
67        $form->setHiddenField('id', $ID);
68
69        // show dropdown selector, whether include not only recent pages but also recent media files?
70        if ($conf['mediarevisions']) {
71            $this->addRecentItemSelector($form);
72        }
73
74        // start listing of recent items
75        $form->addTagOpen('ul');
76        foreach ($recents as $recent) {
77            // check possible external edition for current page or media
78            $this->checkCurrentRevision($recent);
79
80            $RevInfo = new RevisionInfo($recent);
81            $RevInfo->isCurrent(true);
82            $class = ($RevInfo->val('type') === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor': '';
83            $form->addTagOpen('li')->addClass($class);
84            $form->addTagOpen('div')->addClass('li');
85            $html = implode(' ', [
86                $RevInfo->showFileIcon(),          // filetype icon
87                $RevInfo->showEditDate(),          // edit date and time
88                $RevInfo->showIconCompareWithPrevious(),    // link to diff view icon
89                $RevInfo->showIconRevisions(),     // link to revisions icon
90                $RevInfo->showFileName(),          // name of page or media
91                $RevInfo->showEditSummary(),       // edit summary
92                $RevInfo->showEditor(),            // editor info
93                $RevInfo->showSizechange(),        // size change indicator
94            ]);
95            $form->addHTML($html);
96            $form->addTagClose('div');
97            $form->addTagClose('li');
98        }
99        $form->addTagClose('ul');
100
101        $form->addTagClose('div'); // close div class=no
102
103        // provide navigation for paginated recent list (of pages and/or media files)
104        $form->addHTML($this->htmlNavigation($first, $hasNext));
105
106        print $form->toHTML('Recent');
107    }
108
109    /**
110     * Get recent items, and set correct pagination parameters (first, hasNext)
111     *
112     * @param int  $first
113     * @param bool $hasNext
114     * @return array  recent items to be shown in a paginated list
115     *
116     * @see also dokuwiki\Changelog::getRevisionInfo()
117     */
118    protected function getRecents(&$first, &$hasNext)
119    {
120        global $ID, $conf;
121
122        $flags = 0;
123        if ($this->show_changes == 'mediafiles' && $conf['mediarevisions']) {
124            $flags = RECENTS_MEDIA_CHANGES;
125        } elseif ($this->show_changes == 'pages') {
126            $flags = 0;
127        } elseif ($conf['mediarevisions']) {
128            $flags = RECENTS_MEDIA_PAGES_MIXED;
129        }
130
131        /* we need to get one additionally log entry to be able to
132         * decide if this is the last page or is there another one.
133         * This is the cheapest solution to get this information.
134         */
135        $recents = getRecents($first, $conf['recent'] + 1, getNS($ID), $flags);
136        if (count($recents) == 0 && $first != 0) {
137            $first = 0;
138            $recents = getRecents($first, $conf['recent'] + 1, getNS($ID), $flags);
139        }
140
141        $hasNext = false;
142        if (count($recents) > $conf['recent']) {
143            $hasNext = true;
144            array_pop($recents); // remove extra log entry
145        }
146        return $recents;
147    }
148
149    /**
150     * Check possible external deletion for current page or media
151     *
152     * To keep sort order in the recent list, we ignore externally modification.
153     * It is not possible to know when external deletion had happened,
154     * $info['date'] is to be incremented 1 second when such deletion detected.
155     */
156    protected function checkCurrentRevision(array &$info)
157    {
158        $itemType = $info['media'] ? 'media' : 'page';
159        if ($itemType == 'page') {
160            $changelog = new PageChangelog($info['id']);
161        } else {
162            $changelog = new MediaChangelog($info['id']);
163        }
164        if (!$changelog->isCurrentRevision($info['date'])) {
165            $currentRevInfo = $changelog->getCurrentRevisionInfo();
166            if ($currentRevInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
167                // the page or media file was externally deleted, updated info because the link is already red
168                // externally created and edited not updated because sorting by date is not worth so much changes
169                $info = array_merge($info, $currentRevInfo);
170            }
171        }
172        unset($changelog);
173    }
174
175    /**
176     * Navigation buttons for Pagination (prev/next)
177     *
178     * @param int  $first
179     * @param bool $hasNext
180     * @return string html
181     */
182    protected function htmlNavigation($first, $hasNext)
183    {
184        global $conf, $lang;
185
186        $last = $first + $conf['recent'];
187        $html = '<div class="pagenav">';
188        if ($first > 0) {
189            $first = max($first - $conf['recent'], 0);
190            $html.= '<div class="pagenav-prev">';
191            $html.= '<button type="submit" name="first['.$first.']" accesskey="n"'
192                  . ' title="'.$lang['btn_newer'].' [N]" class="button show">'
193                  . $lang['btn_newer']
194                  . '</button>';
195            $html.= '</div>';
196        }
197        if ($hasNext) {
198            $html.= '<div class="pagenav-next">';
199            $html.= '<button type="submit" name="first['.$last.']" accesskey="p"'
200                  . ' title="'.$lang['btn_older'].' [P]" class="button show">'
201                  . $lang['btn_older']
202                  . '</button>';
203            $html.= '</div>';
204        }
205        $html.= '</div>';
206        return $html;
207    }
208
209    /**
210     * Add dropdown selector of item types to the form instance
211     *
212     * @param Form $form
213     * @return void
214     */
215    protected function addRecentItemSelector(Form $form)
216    {
217        global $lang;
218
219        $form->addTagOpen('div')->addClass('changeType');
220        $options = array(
221                    'pages'      => $lang['pages_changes'],
222                    'mediafiles' => $lang['media_changes'],
223                    'both'       => $lang['both_changes'],
224        );
225        $form->addDropdown('show_changes', $options, $lang['changes_type'])
226                ->val($this->show_changes)->addClass('quickselect');
227        $form->addButton('do[recent]', $lang['btn_apply'])->attr('type','submit');
228        $form->addTagClose('div');
229    }
230
231}
232