xref: /dokuwiki/inc/Ui/Revisions.php (revision 5ec961365f9aa233de642a0e46044285ee16a4e7)
1<?php
2
3namespace dokuwiki\Ui;
4
5use dokuwiki\ChangeLog\ChangeLog;
6
7/**
8 * DokuWiki Revisions Interface
9 * parent class of PageRevisions and MediaRevisions
10 *
11 * @package dokuwiki\Ui
12 */
13abstract class Revisions extends Ui
14{
15    /* @var string */
16    protected $id;   // page id or media id
17
18    /* @var ChangeLog */
19    protected $changelog; // PageChangeLog or MediaChangeLog object
20
21    /**
22     * Revisions Ui constructor
23     *
24     * @param string $id  page id or media id
25     */
26    public function __construct($id)
27    {
28        $this->id = $id;
29        $this->setChangeLog();
30    }
31
32    /**
33     * set class property changelog
34     */
35    abstract protected function setChangeLog();
36
37    /**
38     * Get revisions, and set correct pagination parameters (first, hasNext)
39     *
40     * @param int  $first
41     * @param bool $hasNext
42     * @return array  revisions to be shown in a pagenated list
43     * @see also https://www.dokuwiki.org/devel:changelog
44     */
45    protected function getRevisions(&$first, &$hasNext)
46    {
47        global $conf;
48
49        $changelog =& $this->changelog;
50        $revisions = [];
51
52        $currentRevInfo = $changelog->getCurrentRevisionInfo();
53        if (!$currentRevInfo) return $revisions;
54
55        $num = $conf['recent'];
56        if ($first == 0) {
57            $revisions[] = $currentRevInfo;
58            $first += (int)($currentRevInfo['date'] == $changelog->lastRevision());
59            $num = $num - 1;
60        }
61        /* we need to get one additional log entry to be able to
62         * decide if this is the last page or is there another one.
63         * see also Ui\Recent::getRecents()
64         */
65        $revlist = $changelog->getRevisions($first, $num + 1);
66        if (count($revlist) == 0 && $first > 0) {
67            // resets to zero if $first requested a too high number
68            $first = 0;
69            return $this->getRevisions($first, $hasNext);
70        }
71
72        // decide if this is the last page or is there another one
73        $hasNext = false;
74        if (count($revlist) > $num) {
75            $hasNext = true;
76            array_pop($revlist); // remove one additional log entry
77        }
78
79        // append each revison info array to the revisions
80        foreach ($revlist as $rev) {
81            $revisions[] = $changelog->getRevisionInfo($rev);
82        }
83        return $revisions;
84    }
85
86    /**
87     * Navigation buttons for Pagenation (prev/next)
88     *
89     * @param int  $first
90     * @param bool $hasNext
91     * @param callable $callback returns array of hidden fields for the form button
92     * @return string html
93     */
94    protected function navigation($first, $hasNext, $callback)
95    {
96        global $conf;
97
98        $html = '<div class="pagenav">';
99        $last = $first + $conf['recent'];
100        if ($first > 0) {
101            $first = max($first - $conf['recent'], 0);
102            $html.= '<div class="pagenav-prev">';
103            $html.= html_btn('newer', $this->id, "p", $callback($first));
104            $html.= '</div>';
105        }
106        if ($hasNext) {
107            $html.= '<div class="pagenav-next">';
108            $html.= html_btn('older', $this->id, "n", $callback($last));
109            $html.= '</div>';
110        }
111        $html.= '</div>';
112        return $html;
113    }
114
115    /**
116     * Returns instance of objRevInfo
117     *
118     * @param array $info  Revision info structure of a page or media file
119     * @return objRevInfo object (anonymous class)
120     */
121    public function getObjRevInfo(array $info)
122    {
123        return new class ($info) // anonymous class (objRevInfo)
124        {
125            protected $info;
126
127            public function __construct(array $info)
128            {
129                $info['item'] = strrpos($info['id'], '.') ? 'media' : 'page';
130                $info['current'] = $info['current'] ?? false;
131                $this->info = $info;
132            }
133
134            // current indicator
135            public function currentIndicator()
136            {
137                global $lang;
138                return ($this->info['current']) ? '('.$lang['current'].')' : '';
139            }
140
141            // edit date and time of the page or media file
142            public function editDate()
143            {
144                global $lang;
145                $date = dformat($this->info['date']);
146                if (($this->info['timestamp'] ?? '') == 'unknown') {
147                    // externally deleted or older file restored
148                    $date = preg_replace('/[0-9a-zA-Z]/','_', $date);
149                }
150                return '<span class="date">'. $date .'</span>';
151            }
152
153            // edit summary
154            public function editSummary()
155            {
156                return '<span class="sum">'.' – '. hsc($this->info['sum']).'</span>';
157            }
158
159            // editor of the page or media file
160            public function editor()
161            {
162                // slightly different with display of Ui\Recent, i.e. external edit
163                global $lang;
164                $html = '<span class="user">';
165                if (!$this->info['user'] && !$this->info['ip']) {
166                    $html.= '('.$lang['external_edit'].')';
167                } elseif ($this->info['user']) {
168                    $html.= '<bdi>'. editorinfo($this->info['user']) .'</bdi>';
169                    if (auth_ismanager()) $html.= ' <bdo dir="ltr">('. $this->info['ip'] .')</bdo>';
170                } else {
171                    $html.= '<bdo dir="ltr">'. $this->info['ip'] .'</bdo>';
172                }
173                $html.= '</span>';
174                return $html;
175            }
176
177            // name of the page or media file
178            public function itemName()
179            {
180                // slightly different with display of Ui\Recent, i.e. revison may not exists
181                $id = $this->info['id'];
182                $rev = $this->info['date'];
183
184                switch ($this->info['item']) {
185                    case 'media': // media file revision
186                        if ($this->info['current']) {
187                            $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view'], '&');
188                            $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
189                        } elseif (file_exists(mediaFN($id, $rev))) {
190                            $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view', 'rev'=> $rev], '&');
191                            $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>';
192                        } else {
193                            $html = $id;
194                        }
195                        return $html;
196                    case 'page': // page revision
197                        $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
198                        if (!$display_name) $display_name = $id;
199                        if ($this->info['type'] == DOKU_CHANGE_TYPE_DELETE) {
200                            // exteranlly deleted or older file restored
201                            $href = wl($id, "", false, '&');
202                            $html = '<a href="'.$href.'" class="wikilink2">'.$display_name.'</a>';
203                        } elseif ($this->info['current'] || page_exists($id, $rev)) {
204                            $href = wl($id, "rev=$rev", false, '&');
205                            $html = '<a href="'.$href.'" class="wikilink1">'.$display_name.'</a>';
206                        } else {
207                            $html = $display_name;
208                        }
209                        return $html;
210                }
211                return '';
212            }
213
214            // icon difflink
215            public function difflink()
216            {
217                global $lang;
218                $id = $this->info['id'];
219                $rev = $this->info['date'];
220
221                switch ($this->info['item']) {
222                    case 'media': // media file revision
223                        if ($this->info['current'] || !file_exists(mediaFN($id, $rev))) {
224                            $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
225                        } else {
226                            $href = media_managerURL(['image'=> $id, 'rev'=> $rev, 'mediado'=>'diff'], '&');
227                            $html = '<a href="'.$href.'" class="diff_link">'
228                                  . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
229                                  . ' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />'
230                                  . '</a> ';
231                        }
232                        return $html;
233                    case 'page': // page revision
234                        if ($this->info['current'] || !page_exists($id, $rev)) {
235                            $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
236                        } else {
237                            $href = wl($id, "rev=$rev,do=diff", false, '&');
238                            $html = '<a href="'.$href.'" class="diff_link">'
239                                  . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
240                                  . ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />'
241                                  . '</a>';
242                        }
243                        return $html;
244                }
245                return '';
246            }
247
248            // size change
249            public function sizeChange()
250            {
251                $class = 'sizechange';
252                $value = filesize_h(abs($this->info['sizechange']));
253                if ($this->info['sizechange'] > 0) {
254                    $class .= ' positive';
255                    $value = '+' . $value;
256                } elseif ($this->info['sizechange'] < 0) {
257                    $class .= ' negative';
258                    $value = '-' . $value;
259                } else {
260                    $value = '±' . $value;
261                }
262                return '<span class="'.$class.'">'.$value.'</span>';
263            }
264        }; // end of anonymous class (objRevInfo)
265    }
266
267}
268