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