1 <?php
2 
3 namespace dokuwiki\Ui;
4 
5 use dokuwiki\ChangeLog\MediaChangeLog;
6 use dokuwiki\ChangeLog\PageChangeLog;
7 use dokuwiki\ChangeLog\RevisionInfo;
8 use dokuwiki\Form\Form;
9 
10 /**
11  * DokuWiki Recent Interface
12  *
13  * @package dokuwiki\Ui
14  */
15 class 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      * @return void
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      * @author Andreas Gohr <andi@splitbrain.org>
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         echo p_locale_xhtml('recent');
55 
56         if (getNS($ID) != '') {
57             echo '<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         echo $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         if ($info['mode'] == RevisionInfo::MODE_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 && $currentRevInfo['type'] == DOKU_CHANGE_TYPE_DELETE) {
166                 // the page or media file was externally deleted, updated info because the link is already red
167                 // externally created and edited not updated because sorting by date is not worth so much changes
168                 $info = array_merge($info, $currentRevInfo);
169             }
170         }
171         unset($changelog);
172     }
173 
174     /**
175      * Navigation buttons for Pagination (prev/next)
176      *
177      * @param int $first
178      * @param bool $hasNext
179      * @return string html
180      */
181     protected function htmlNavigation($first, $hasNext)
182     {
183         global $conf, $lang;
184 
185         $last = $first + $conf['recent'];
186         $html = '<div class="pagenav">';
187         if ($first > 0) {
188             $first = max($first - $conf['recent'], 0);
189             $html .= '<div class="pagenav-prev">';
190             $html .= '<button type="submit" name="first[' . $first . ']" accesskey="n"'
191                 . ' title="' . $lang['btn_newer'] . ' [N]" class="button show">'
192                 . $lang['btn_newer']
193                 . '</button>';
194             $html .= '</div>';
195         }
196         if ($hasNext) {
197             $html .= '<div class="pagenav-next">';
198             $html .= '<button type="submit" name="first[' . $last . ']" accesskey="p"'
199                 . ' title="' . $lang['btn_older'] . ' [P]" class="button show">'
200                 . $lang['btn_older']
201                 . '</button>';
202             $html .= '</div>';
203         }
204         $html .= '</div>';
205         return $html;
206     }
207 
208     /**
209      * Add dropdown selector of item types to the form instance
210      *
211      * @param Form $form
212      * @return void
213      */
214     protected function addRecentItemSelector(Form $form)
215     {
216         global $lang;
217 
218         $form->addTagOpen('div')->addClass('changeType');
219         $options = [
220             'pages' => $lang['pages_changes'],
221             'mediafiles' => $lang['media_changes'],
222             'both' => $lang['both_changes']
223         ];
224         $form->addDropdown('show_changes', $options, $lang['changes_type'])
225             ->val($this->show_changes)->addClass('quickselect');
226         $form->addButton('do[recent]', $lang['btn_apply'])->attr('type', 'submit');
227         $form->addTagClose('div');
228     }
229 }
230