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