xref: /dokuwiki/inc/ChangeLog/RevisionInfo.php (revision 54cc7aa41e0f453bd6887b0e79242a139d84a47a)
1<?php
2
3namespace dokuwiki\ChangeLog;
4
5/**
6 * Class RevisionInfo
7 *
8 * Provides methods to show Revision Information in DokuWiki Ui components:
9 *  - Ui\Recent
10 *  - Ui\PageRevisions
11 *  - Ui\MediaRevisions
12 */
13class RevisionInfo
14{
15    /* @var array */
16    protected $info;
17
18    /**
19     * Constructor
20     *
21     * @param array $info Revision Information structure with entries:
22     *      - date:  unix timestamp
23     *      - ip:    IPv4 or IPv6 address
24     *      - type:  change type (log line type)
25     *      - id:    page id
26     *      - user:  user name
27     *      - sum:   edit summary (or action reason)
28     *      - extra: extra data (varies by line type)
29     *      - sizechange: change of filesize
30     *      additionally,
31     *      - current:   (optional) whether current revision or not
32     *      - timestamp: (optional) set only when external edits occurred
33     *      - mode:  (internal use) ether "media" or "page"
34     */
35    public function __construct($info = null)
36    {
37        if (is_array($info) && isset($info['id'])) {
38            // define strategy context
39            $info['mode'] = $info['media'] ? 'media' : 'page';
40        } else {
41            $info = [
42                'mode' => 'page',
43                'date' => false,
44            ];
45        }
46        $this->info = $info;
47    }
48
49    /**
50     * Set or return whether this revision is current page or media file
51     *
52     * This method does not check exactly whether the revision is current or not. Instead,
53     * set value of associated "current" key for internal use. Some UI element like diff
54     * link button depend on relation to current page or media file. A changelog line does
55     * not indicate whether it corresponds to current page or media file.
56     *
57     * @param bool $value true if the revision is current, otherwise false
58     * @return bool
59     */
60    public function isCurrent($value = null)
61    {
62        return (bool) $this->val('current', $value);
63    }
64
65    /**
66     * Return or set a value of associated key of revision information
67     * but does not allow to change values of existing keys
68     *
69     * @param string $key
70     * @param mixed $value
71     * @return string|null
72     */
73    public function val($key, $value = null)
74    {
75        if (isset($value) && !array_key_exists($key, $this->info)) {
76            // setter, only for new keys
77            $this->info[$key] = $value;
78        }
79        if (array_key_exists($key, $this->info)) {
80            // getter
81            return $this->info[$key];
82        }
83        return null;
84    }
85
86    /**
87     * Set extra key-value to the revision information
88     * but does not allow to change values of existing keys
89     * @param array $info
90     * @return void
91     */
92    public function append(array $info)
93    {
94        foreach ($info as $key => $value) {
95            $this->val($key, $value);
96        }
97    }
98
99
100    /**
101     * file icon of the page or media file
102     * used in [Ui\recent]
103     *
104     * @return string
105     */
106    public function showFileIcon()
107    {
108        $id = $this->val('id');
109        if ($this->val('mode') == 'media') {
110            // media file revision
111            return media_printicon($id);
112        } elseif ($this->val('mode') == 'page') {
113            // page revision
114            return '<img class="icon" src="'.DOKU_BASE.'lib/images/fileicons/file.png" alt="'.$id.'" />';
115        }
116    }
117
118    /**
119     * edit date and time of the page or media file
120     * used in [Ui\recent, Ui\Revisions]
121     *
122     * @param bool $checkTimestamp  enable timestamp check, alter formatted string when timestamp is false
123     * @return string
124     */
125    public function showEditDate($checkTimestamp = false)
126    {
127        $formatted = dformat($this->val('date'));
128        if ($checkTimestamp && $this->val('timestamp') === false) {
129            // exact date is unknown for externally deleted file
130            // when unknown, alter formatted string "YYYY-mm-DD HH:MM" to "____-__-__ __:__"
131            $formatted = preg_replace('/[0-9a-zA-Z]/','_', $formatted);
132        }
133        return '<span class="date">'. $formatted .'</span>';
134    }
135
136    /**
137     * edit summary
138     * used in [Ui\recent, Ui\Revisions]
139     *
140     * @return string
141     */
142    public function showEditSummary()
143    {
144        return '<span class="sum">'.' – '. hsc($this->val('sum')).'</span>';
145    }
146
147    /**
148     * editor of the page or media file
149     * used in [Ui\recent, Ui\Revisions]
150     *
151     * @return string
152     */
153    public function showEditor()
154    {
155        if ($this->val('user')) {
156            $html = '<bdi>'. editorinfo($this->val('user')) .'</bdi>';
157            if (auth_ismanager()) $html .= ' <bdo dir="ltr">('. $this->val('ip') .')</bdo>';
158        } else {
159            $html = '<bdo dir="ltr">'. $this->val('ip') .'</bdo>';
160        }
161        return '<span class="user">'. $html. '</span>';
162    }
163
164    /**
165     * name of the page or media file
166     * used in [Ui\recent, Ui\Revisions]
167     *
168     * @return string
169     */
170    public function showFileName()
171    {
172        $id = $this->val('id');
173        $rev = $this->isCurrent() ? '' : $this->val('date');
174
175        if ($this->val('mode') == 'media') {
176            // media file revision
177            $params = ['tab_details'=> 'view', 'ns'=> getNS($id), 'image'=> $id];
178            if ($rev) $params += ['rev'=> $rev];
179            $href = media_managerURL($params, '&');
180            $display_name = $id;
181            $exists = file_exists(mediaFN($id, $rev));
182        } elseif ($this->val('mode') == 'page') {
183            // page revision
184            $params = $rev ? ['rev'=> $rev] : [];
185            $href = wl($id, $params, false, '&');
186            $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id;
187            if (!$display_name) $display_name = $id;
188            $exists = page_exists($id, $rev);
189        }
190
191        if ($exists) {
192            $class = 'wikilink1';
193        } elseif ($this->isCurrent()) {
194            //show only not-existing link for current page, which allows for directly create a new page/upload
195            $class = 'wikilink2';
196        } else {
197            //revision is not in attic
198            return $display_name;
199        }
200        if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
201            $class = 'wikilink2';
202        }
203        return '<a href="'.$href.'" class="'.$class.'">'.$display_name.'</a>';
204    }
205
206    /**
207     * Revision Title for PageDiff table headline
208     *
209     * @return string
210     */
211    public function showRevisionTitle()
212    {
213        global $lang;
214
215        if (!$this->val('date')) return '&mdash;';
216
217        $id = $this->val('id');
218        $rev = $this->isCurrent() ? '' : $this->val('date');
219        $params = ($rev) ? ['rev'=> $rev] : [];
220
221        // revision info may have timestamp key when external edits occurred
222        $date = ($this->val('timestamp') === false)
223            ? $lang['unknowndate']
224            : dformat($this->val('date'));
225
226
227        if ($this->val('mode') == 'media') {
228            // media file revision
229            $href = ml($id, $params, false, '&');
230            $exists = file_exists(mediaFN($id, $rev));
231        } elseif ($this->val('mode') == 'page') {
232            // page revision
233            $href = wl($id, $params, false, '&');
234            $exists = page_exists($id, $rev);
235        }
236        if ($exists) {
237            $class = 'wikilink1';
238        } elseif ($this->isCurrent()) {
239            //show only not-existing link for current page, which allows for directly create a new page/upload
240            $class = 'wikilink2';
241        } else {
242            //revision is not in attic
243            return $id.' ['.$date.']';
244        }
245        if ($this->val('type') == DOKU_CHANGE_TYPE_DELETE) {
246            $class = 'wikilink2';
247        }
248        return '<bdi><a class="'.$class.'" href="'.$href.'">'.$id.' ['.$date.']'.'</a></bdi>';
249    }
250
251    /**
252     * diff link icon in recent changes list, to compare (this) current revision with previous one
253     * all items in "recent changes" are current revision of the page or media
254     *
255     * @return string
256     */
257    public function showIconCompareWithPrevious()
258    {
259        global $lang;
260        $id = $this->val('id');
261
262        $href = '';
263        if ($this->val('mode') == 'media') {
264            // media file revision
265            // unlike page, media file does not copied to media_attic when uploaded.
266            // diff icon will not be shown when external edit occurred
267            // because no attic file to be compared with current.
268            $revs = (new MediaChangeLog($id))->getRevisions(0, 1);
269            $showLink = (count($revs) && file_exists(mediaFN($id,$revs[0])) && file_exists(mediaFN($id)));
270            if ($showLink) {
271                $param = ['tab_details'=>'history', 'mediado'=>'diff', 'ns'=> getNS($id), 'image'=> $id];
272                $href = media_managerURL($param, '&');
273            }
274        } elseif ($this->val('mode') == 'page') {
275            // page revision
276            // when a page just created anyway, it is natural to expect no older revisions
277            // even if it had once existed but deleted before. Simply ignore to check changelog.
278            if ($this->val('type') !== DOKU_CHANGE_TYPE_CREATE) {
279                $href = wl($id, ['do'=>'diff'], false, '&');
280            }
281        }
282
283        if ($href) {
284            return '<a href="'.$href.'" class="diff_link">'
285                  .'<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
286                  .' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />'
287                  .'</a>';
288        } else {
289            return '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
290        }
291    }
292
293    /**
294     * diff link icon in revisions list, compare this revision with current one
295     * the icon does not displayed for the current revision
296     *
297     * @return string
298     */
299    public function showIconCompareWithCurrent()
300    {
301        global $lang;
302        $id = $this->val('id');
303        $rev = $this->isCurrent() ? '' : $this->val('date');
304
305        $href = '';
306        if ($this->val('mode') == 'media') {
307            // media file revision
308            if (!$this->isCurrent() && file_exists(mediaFN($id, $rev))) {
309                $param = ['mediado'=>'diff', 'image'=> $id, 'rev'=> $rev];
310                $href = media_managerURL($param, '&');
311            }
312        } elseif ($this->val('mode') == 'page') {
313            // page revision
314            if (!$this->isCurrent()) {
315                $href = wl($id, ['rev'=> $rev, 'do'=>'diff'], false, '&');
316            }
317        }
318
319        if ($href) {
320            return '<a href="'.$href.'" class="diff_link">'
321                  .'<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"'
322                  .' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />'
323                  .'</a>';
324        } else {
325            return '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />';
326        }
327    }
328
329    /**
330     * icon for revision action
331     * used in [Ui\recent]
332     *
333     * @return string
334     */
335    public function showIconRevisions()
336    {
337        global $lang;
338
339        if (!actionOK('revisions')) {
340            return '';
341        }
342
343        $id = $this->val('id');
344        if ($this->val('mode') == 'media') {
345            // media file revision
346            $param  = ['tab_details'=>'history', 'ns'=> getNS($id), 'image'=> $id];
347            $href = media_managerURL($param, '&');
348        } elseif ($this->val('mode') == 'page') {
349            // page revision
350            $href = wl($id, ['do'=>'revisions'], false, '&');
351        }
352        return '<a href="'.$href.'" class="revisions_link">'
353              . '<img src="'.DOKU_BASE.'lib/images/history.png" width="12" height="14"'
354              . ' title="'.$lang['btn_revs'].'" alt="'.$lang['btn_revs'].'" />'
355              . '</a>';
356    }
357
358    /**
359     * size change
360     * used in [Ui\recent, Ui\Revisions]
361     *
362     * @return string
363     */
364    public function showSizeChange()
365    {
366        $class = 'sizechange';
367        $value = filesize_h(abs($this->val('sizechange')));
368        if ($this->val('sizechange') > 0) {
369            $class .= ' positive';
370            $value = '+' . $value;
371        } elseif ($this->val('sizechange') < 0) {
372            $class .= ' negative';
373            $value = '-' . $value;
374        } else {
375            $value = '±' . $value;
376        }
377        return '<span class="'.$class.'">'.$value.'</span>';
378    }
379
380    /**
381     * current indicator, used in revision list
382     * not used in Ui\Recent because recent files are always current one
383     *
384     * @return string
385     */
386    public function showCurrentIndicator()
387    {
388        global $lang;
389        return $this->isCurrent() ? '('.$lang['current'].')' : '';
390    }
391
392
393}
394