xref: /dokuwiki/inc/Ui/MediaDiff.php (revision a7b2005af9c5943ba9bb7b178697e7b36de9200f)
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 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        $changelog =& $this->changelog;
238
239        $ns = getNS($this->id);
240        $auth = auth_quickaclcheck("$ns:*");
241
242        // use '' for current revision
243        [$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']];
244
245        $oldRevMeta = new JpegMeta(mediaFN($this->id, $oldRev));
246        $newRevMeta = new JpegMeta(mediaFN($this->id, $newRev));
247
248        // display diff view table
249        echo '<div class="table">';
250        echo '<table>';
251        echo '<tr>';
252        echo '<th>'. $this->oldRevInfo['navTitle'] .'</th>';
253        echo '<th>'. $this->newRevInfo['navTitle'] .'</th>';
254        echo '</tr>';
255
256        echo '<tr class="image">';
257        echo '<td>';
258        media_preview($this->id, $auth, $oldRev, $oldRevMeta); // $auth not used in media_preview()?
259        echo '</td>';
260
261        echo '<td>';
262        media_preview($this->id, $auth, $newRev, $newRevMeta);
263        echo '</td>';
264        echo '</tr>';
265
266        echo '<tr class="actions">';
267        echo '<td>';
268        media_preview_buttons($this->id, $auth, $oldRev); // $auth used in media_preview_buttons()
269        echo '</td>';
270
271        echo '<td>';
272        media_preview_buttons($this->id, $auth, $newRev);
273        echo '</td>';
274        echo '</tr>';
275
276        $l_tags = media_file_tags($oldRevMeta);
277        $r_tags = media_file_tags($newRevMeta);
278        // FIXME r_tags-only stuff
279        foreach ($l_tags as $key => $l_tag) {
280            if ($l_tag['value'] != $r_tags[$key]['value']) {
281                $r_tags[$key]['highlighted'] = true;
282                $l_tags[$key]['highlighted'] = true;
283            } elseif (!$l_tag['value'] || !$r_tags[$key]['value']) {
284                unset($r_tags[$key]);
285                unset($l_tags[$key]);
286            }
287        }
288
289        echo '<tr>';
290        foreach (array($l_tags, $r_tags) as $tags) {
291            echo '<td>';
292
293            echo '<dl class="img_tags">';
294            foreach ($tags as $tag) {
295                $value = cleanText($tag['value']);
296                if (!$value) $value = '-';
297                echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
298                echo '<dd>';
299                if ($tag['highlighted']) echo '<strong>';
300                if ($tag['tag'][2] == 'date') {
301                    echo dformat($value);
302                } else {
303                    echo hsc($value);
304                }
305                if ($tag['highlighted']) echo '</strong>';
306                echo '</dd>';
307            }
308            echo '</dl>';
309
310            echo '</td>';
311        }
312        echo '</tr>';
313
314        echo '</table>';
315        echo '</div>';
316    }
317
318    /**
319     * Revision Title for MediaDiff table headline
320     *
321     * @param array $info  Revision info structure of a media file
322     * @return string
323     */
324    protected function revisionTitle(array $info)
325    {
326        global $lang, $INFO;
327
328        if (isset($info['date'])) {
329            $rev = $info['date'];
330            $title = '<bdi><a class="wikilink1" href="'.ml($this->id, ['rev' => $rev]).'">'
331                   . dformat($rev).'</a></bdi>';
332        } else {
333            $rev = false;
334            $title = '&mdash;';
335        }
336        if (isset($info['current']) || ($rev && $rev == $INFO['currentrev'])) {
337            $title .= '&nbsp;('.$lang['current'].')';
338        }
339
340        // append separator
341        $title .= ($this->preference['difftype'] === 'inline') ? ' ' : '<br />';
342
343        // supplement
344        if (isset($info['date'])) {
345            $objRevInfo = (new MediaRevisions($this->id))->getObjRevInfo($info);
346            $title .= $objRevInfo->editSummary().' '.$objRevInfo->editor();
347        }
348        return $title;
349    }
350
351}
352