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