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