xref: /dokuwiki/inc/Ui/MediaDiff.php (revision 79a2d7845d5e5e48fe3be8f192717de9294aaba5)
1<?php
2
3namespace dokuwiki\Ui;
4
5use dokuwiki\ChangeLog\MediaChangeLog;
6use dokuwiki\Form\Form;
7use InvalidArgumentException;
8use JpegMeta;
9
10/**
11 * DokuWiki MediaDiff Interface
12 *
13 * @package dokuwiki\Ui
14 */
15class MediaDiff extends Diff
16{
17    /* @var MediaChangeLog */
18    protected $changelog;
19
20    /* @var array */
21    protected $oldRevInfo;
22    protected $newRevInfo;
23
24    /* @var bool */
25    protected $is_img;
26
27    /**
28     * MediaDiff Ui constructor
29     *
30     * @param string $id  media id
31     */
32    public function __construct($id)
33    {
34        if (!isset($id)) {
35            throw new InvalidArgumentException('media id should not be empty!');
36        }
37
38        // init preference
39        $this->preference['fromAjax'] = false;  // see dokuwiki\Ajax::callMediadiff()
40        $this->preference['showIntro'] = false;
41        $this->preference['difftype'] = 'both'; // diff view type: both, opacity or portions
42
43        parent::__construct($id);
44    }
45
46    /** @inheritdoc */
47    protected function setChangeLog()
48    {
49        $this->changelog = new MediaChangeLog($this->id);
50    }
51
52    /**
53     * Handle requested revision(s) and diff view preferences
54     *
55     * @return void
56     */
57    protected function handle()
58    {
59        global $INPUT;
60
61        // requested rev or rev2
62        parent::handle();
63
64        // requested diff view type
65        if ($INPUT->has('difftype')) {
66            $this->preference['difftype'] = $INPUT->str('difftype');
67        }
68    }
69
70    /**
71     * Prepare revision info of comparison pair
72     */
73    protected function preProcess()
74    {
75        $changelog =& $this->changelog;
76
77        // revision info of older file (left side)
78        $this->oldRevInfo = $changelog->getRevisionInfo($this->oldRev);
79        // revision info of newer file (right side)
80        $this->newRevInfo = $changelog->getRevisionInfo($this->newRev);
81
82        $this->is_img = preg_match('/\.(jpe?g|gif|png)$/', $this->id);
83
84        foreach ([&$this->oldRevInfo, &$this->newRevInfo] as &$revInfo) {
85            // use timestamp and '' properly as $rev for the current file
86            $isCurrent = $changelog->isCurrentRevision($revInfo['date']);
87            $revInfo += [
88                'current' => $isCurrent,
89                'rev'     => $isCurrent ? '' : $revInfo['date'],
90            ];
91
92            // headline in the Diff view navigation
93            $revInfo['navTitle'] = $this->revisionTitle($revInfo);
94
95            if ($this->is_img) {
96                $rev = $revInfo['rev'];
97                $meta = new JpegMeta(mediaFN($this->id, $rev));
98                // get image width and height for the mediamanager preview panel
99                $revInfo['previewSize'] = media_image_preview_size($this->id, $rev, $meta);
100            }
101        }
102        unset($revInfo);
103
104        // re-check image, ensure minimum image width for showImageDiff()
105        $this->is_img = ($this->is_img
106            && ($this->oldRevInfo['previewSize'][0] ?? 0) >= 30
107            && ($this->newRevInfo['previewSize'][0] ?? 0) >= 30
108        );
109        // adjust requested diff view type
110        if (!$this->is_img) {
111            $this->preference['difftype'] = 'both';
112        }
113    }
114
115
116    /**
117     * Shows difference between two revisions of media
118     *
119     * @author Kate Arzamastseva <pshns@ukr.net>
120     */
121    public function show()
122    {
123        global $conf;
124
125        $ns = getNS($this->id);
126        $auth = auth_quickaclcheck("$ns:*");
127
128        if ($auth < AUTH_READ || !$this->id || !$conf['mediarevisions']) return;
129
130        // retrieve form parameters: rev, rev2, difftype
131        $this->handle();
132        // prepare revision info of comparison pair
133        $this->preProcess();
134
135        // display intro
136        if ($this->preference['showIntro']) echo p_locale_xhtml('diff');
137
138        // print form to choose diff view type
139        if ($this->is_img && !$this->preference['fromAjax']) {
140            $this->showDiffViewSelector();
141            echo '<div id="mediamanager__diff" >';
142        }
143
144        switch ($this->preference['difftype']) {
145            case 'opacity':
146            case 'portions':
147                $this->showImageDiff();
148                break;
149            case 'both':
150            default:
151                $this->showFileDiff();
152                break;
153        }
154
155        if ($this->is_img && !$this->preference['fromAjax']) {
156            echo '</div>';
157        }
158    }
159
160    /**
161     * Print form to choose diff view type
162     * the dropdown is to be added through JavaScript, see lib/scripts/media.js
163     */
164    protected function showDiffViewSelector()
165    {
166        // use timestamp for current revision
167        [$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['date']];
168
169        echo '<div class="diffoptions group">';
170
171        $form = new Form([
172            'id' => 'mediamanager__form_diffview',
173            'action' => media_managerURL([], '&'),
174            'method' => 'get',
175            'class' => 'diffView',
176        ]);
177        $form->addTagOpen('div')->addClass('no');
178        $form->setHiddenField('sectok', null);
179        $form->setHiddenField('mediado', 'diff');
180        $form->setHiddenField('rev2[0]', $oldRev);
181        $form->setHiddenField('rev2[1]', $newRev);
182        $form->addTagClose('div');
183        echo $form->toHTML();
184
185        echo '</div>'; // .diffoptions
186    }
187
188    /**
189     * Prints two images side by side
190     * and slider
191     *
192     * @author Kate Arzamastseva <pshns@ukr.net>
193     */
194    protected function showImageDiff()
195    {
196        // diff view type: opacity or portions
197        $type = $this->preference['difftype'];
198
199        // use '' for current revision
200        [$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']];
201
202        // adjust image width, right side (newer) has priority
203        $oldRevSize = $this->oldRevInfo['previewSize'];
204        $newRevSize = $this->newRevInfo['previewSize'];
205        if ($oldRevSize != $newRevSize) {
206            if ($newRevSize[0] > $oldRevSize[0]) {
207                $oldRevSize = $newRevSize;
208            }
209        }
210
211        $oldRevSrc = ml($this->id, ['rev' => $oldRev, 'h' => $oldRevSize[1], 'w' => $oldRevSize[0]]);
212        $newRevSrc = ml($this->id, ['rev' => $newRev, 'h' => $oldRevSize[1], 'w' => $oldRevSize[0]]);
213
214        // slider
215        echo '<div class="slider" style="max-width: '.($oldRevSize[0]-20).'px;" ></div>';
216
217        // two images in divs
218        echo '<div class="imageDiff '.$type.'">';
219        echo '<div class="image1" style="max-width: '.$oldRevSize[0].'px;">';
220        echo '<img src="'.$oldRevSrc.'" alt="" />';
221        echo '</div>';
222        echo '<div class="image2" style="max-width: '.$oldRevSize[0].'px;">';
223        echo '<img src="'.$newRevSrc.'" alt="" />';
224        echo '</div>';
225        echo '</div>';
226    }
227
228    /**
229     * Shows difference between two revisions of media file
230     *
231     * @author Kate Arzamastseva <pshns@ukr.net>
232     */
233    protected function showFileDiff()
234    {
235        global $lang;
236
237        $ns = getNS($this->id);
238        $auth = auth_quickaclcheck("$ns:*");
239
240        // use '' for current revision
241        [$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']];
242
243        $oldRevMeta = new JpegMeta(mediaFN($this->id, $oldRev));
244        $newRevMeta = new JpegMeta(mediaFN($this->id, $newRev));
245
246        // display diff view table
247        echo '<div class="table">';
248        echo '<table>';
249        echo '<tr>';
250        echo '<th>'. $this->oldRevInfo['navTitle'] .'</th>';
251        echo '<th>'. $this->newRevInfo['navTitle'] .'</th>';
252        echo '</tr>';
253
254        echo '<tr class="image">';
255        echo '<td>';
256        media_preview($this->id, $auth, $oldRev, $oldRevMeta); // $auth not used in media_preview()?
257        echo '</td>';
258
259        echo '<td>';
260        media_preview($this->id, $auth, $newRev, $newRevMeta);
261        echo '</td>';
262        echo '</tr>';
263
264        echo '<tr class="actions">';
265        echo '<td>';
266        media_preview_buttons($this->id, $auth, $oldRev); // $auth used in media_preview_buttons()
267        echo '</td>';
268
269        echo '<td>';
270        media_preview_buttons($this->id, $auth, $newRev);
271        echo '</td>';
272        echo '</tr>';
273
274        $l_tags = media_file_tags($oldRevMeta);
275        $r_tags = media_file_tags($newRevMeta);
276        // FIXME r_tags-only stuff
277        foreach ($l_tags as $key => $l_tag) {
278            if ($l_tag['value'] != $r_tags[$key]['value']) {
279                $r_tags[$key]['highlighted'] = true;
280                $l_tags[$key]['highlighted'] = true;
281            } elseif (!$l_tag['value'] || !$r_tags[$key]['value']) {
282                unset($r_tags[$key]);
283                unset($l_tags[$key]);
284            }
285        }
286
287        echo '<tr>';
288        foreach (array($l_tags, $r_tags) as $tags) {
289            echo '<td>';
290
291            echo '<dl class="img_tags">';
292            foreach ($tags as $tag) {
293                $value = cleanText($tag['value']);
294                if (!$value) $value = '-';
295                echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
296                echo '<dd>';
297                if ($tag['highlighted']) echo '<strong>';
298                if ($tag['tag'][2] == 'date') {
299                    echo dformat($value);
300                } else {
301                    echo hsc($value);
302                }
303                if ($tag['highlighted']) echo '</strong>';
304                echo '</dd>';
305            }
306            echo '</dl>';
307
308            echo '</td>';
309        }
310        echo '</tr>';
311
312        echo '</table>';
313        echo '</div>';
314    }
315
316    /**
317     * Revision Title for MediaDiff table headline
318     *
319     * @param array $info  Revision info structure of a media file
320     * @return string
321     */
322    protected function revisionTitle(array $info)
323    {
324        global $lang, $INFO;
325
326        if (isset($info['date'])) {
327            $rev = $info['date'];
328            $title = '<bdi><a class="wikilink1" href="'.ml($this->id, ['rev' => $rev]).'">'
329                   . dformat($rev).'</a></bdi>';
330        } else {
331            $rev = false;
332            $title = '&mdash;';
333        }
334        if (isset($info['current']) || ($rev && $rev == $INFO['currentrev'])) {
335            $title .= '&nbsp;('.$lang['current'].')';
336        }
337
338        // append separator
339        $title .= ($this->preference['difftype'] === 'inline') ? ' ' : '<br />';
340
341        // supplement
342        if (isset($info['date'])) {
343            $objRevInfo = (new MediaRevisions($this->id))->getObjRevInfo($info);
344            $title .= $objRevInfo->editSummary().' '.$objRevInfo->editor();
345        }
346        return $title;
347    }
348
349}
350