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