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 RevisionInfo older revision */
22    protected $RevInfo1;
23    /* @var RevisionInfo newer revision */
24    protected $RevInfo2;
25
26    /* @var bool */
27    protected $is_img;
28
29    /**
30     * MediaDiff Ui constructor
31     *
32     * @param string $id media id
33     */
34    public function __construct($id)
35    {
36        if (!isset($id)) {
37            throw new InvalidArgumentException('media id should not be empty!');
38        }
39
40        // init preference
41        $this->preference['fromAjax'] = false;  // see dokuwiki\Ajax::callMediadiff()
42        $this->preference['showIntro'] = false;
43        $this->preference['difftype'] = 'both'; // diff view type: both, opacity or portions
44
45        parent::__construct($id);
46    }
47
48    /** @inheritdoc */
49    protected function setChangeLog()
50    {
51        $this->changelog = new MediaChangeLog($this->id);
52    }
53
54    /**
55     * Handle requested revision(s) and diff view preferences
56     *
57     * @return void
58     */
59    protected function handle()
60    {
61        global $INPUT;
62
63        // retrieve requested rev or rev2
64        parent::handle();
65
66        // requested diff view type
67        if ($INPUT->has('difftype')) {
68            $this->preference['difftype'] = $INPUT->str('difftype');
69        }
70    }
71
72    /**
73     * Prepare revision info of comparison pair
74     */
75    protected function preProcess()
76    {
77        $changelog =& $this->changelog;
78
79        // create revision info object for older and newer sides
80        // RevInfo1 : older, left side
81        // RevInfo2 : newer, right side
82
83        $changelogRev1 = $changelog->getRevisionInfo($this->rev1);
84        $changelogRev2 = $changelog->getRevisionInfo($this->rev2);
85
86        $this->RevInfo1 = new RevisionInfo($changelogRev1);
87        $this->RevInfo2 = new RevisionInfo($changelogRev2);
88
89        $this->is_img = preg_match('/\.(jpe?g|gif|png)$/', $this->id);
90
91        foreach ([$this->RevInfo1, $this->RevInfo2] as $RevInfo) {
92            $isCurrent = $changelog->isCurrentRevision($RevInfo->val('date'));
93            $RevInfo->isCurrent($isCurrent);
94
95            if ($this->is_img) {
96                $rev = $isCurrent ? '' : $RevInfo->val('date');
97                $meta = new JpegMeta(mediaFN($this->id, $rev));
98                // get image width and height for the media manager preview panel
99                $RevInfo->append([
100                    'previewSize' => media_image_preview_size($this->id, $rev, $meta)
101                ]);
102            }
103        }
104
105        // re-check image, ensure minimum image width for showImageDiff()
106        $this->is_img = ($this->is_img
107            && ($this->RevInfo1->val('previewSize')[0] ?? 0) >= 30
108            && ($this->RevInfo2->val('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, date may be false when revisions < 2
168        [$rev1, $rev2] = [(int)$this->RevInfo1->val('date'), (int)$this->RevInfo2->val('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]', $rev1);
182        $form->setHiddenField('rev2[1]', $rev2);
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        $rev1 = $this->RevInfo1->isCurrent() ? '' : $this->RevInfo1->val('date');
198        $rev2 = $this->RevInfo2->isCurrent() ? '' : $this->RevInfo2->val('date');
199
200        // diff view type: opacity or portions
201        $type = $this->preference['difftype'];
202
203        // adjust image width, right side (newer) has priority
204        $rev1Size = $this->RevInfo1->val('previewSize');
205        $rev2Size = $this->RevInfo2->val('previewSize');
206        if ($rev1Size != $rev2Size) {
207            if ($rev2Size[0] > $rev1Size[0]) {
208                $rev1Size = $rev2Size;
209            }
210        }
211
212        $rev1Src = ml($this->id, ['rev' => $rev1, 'h' => $rev1Size[1], 'w' => $rev1Size[0]]);
213        $rev2Src = ml($this->id, ['rev' => $rev2, 'h' => $rev1Size[1], 'w' => $rev1Size[0]]);
214
215        // slider
216        echo '<div class="slider" style="max-width: ' . ($rev1Size[0] - 20) . 'px;" ></div>';
217
218        // two images in divs
219        echo '<div class="imageDiff ' . $type . '">';
220        echo '<div class="image1" style="max-width: ' . $rev1Size[0] . 'px;">';
221        echo '<img src="' . $rev1Src . '" alt="" />';
222        echo '</div>';
223        echo '<div class="image2" style="max-width: ' . $rev1Size[0] . 'px;">';
224        echo '<img src="' . $rev2Src . '" 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        $rev1 = $this->RevInfo1->isCurrent() ? '' : (int)$this->RevInfo1->val('date');
242        $rev2 = $this->RevInfo2->isCurrent() ? '' : (int)$this->RevInfo2->val('date');
243
244        // revision title
245        $rev1Title = trim($this->RevInfo1->showRevisionTitle() . ' ' . $this->RevInfo1->showCurrentIndicator());
246        $rev1Summary = ($this->RevInfo1->val('date'))
247            ? $this->RevInfo1->showEditSummary() . ' ' . $this->RevInfo1->showEditor()
248            : '';
249        $rev2Title = trim($this->RevInfo2->showRevisionTitle() . ' ' . $this->RevInfo2->showCurrentIndicator());
250        $rev2Summary = ($this->RevInfo2->val('date'))
251            ? $this->RevInfo2->showEditSummary() . ' ' . $this->RevInfo2->showEditor()
252            : '';
253
254        $rev1Meta = new JpegMeta(mediaFN($this->id, $rev1));
255        $rev2Meta = new JpegMeta(mediaFN($this->id, $rev2));
256
257        // display diff view table
258        echo '<div class="table">';
259        echo '<table>';
260        echo '<tr>';
261        echo '<th>' . $rev1Title . ' ' . $rev1Summary . '</th>';
262        echo '<th>' . $rev2Title . ' ' . $rev2Summary . '</th>';
263        echo '</tr>';
264
265        echo '<tr class="image">';
266        echo '<td>';
267        media_preview($this->id, $auth, $rev1, $rev1Meta); // $auth not used in media_preview()?
268        echo '</td>';
269
270        echo '<td>';
271        media_preview($this->id, $auth, $rev2, $rev2Meta);
272        echo '</td>';
273        echo '</tr>';
274
275        echo '<tr class="actions">';
276        echo '<td>';
277        media_preview_buttons($this->id, $auth, $rev1); // $auth used in media_preview_buttons()
278        echo '</td>';
279
280        echo '<td>';
281        media_preview_buttons($this->id, $auth, $rev2);
282        echo '</td>';
283        echo '</tr>';
284
285        $rev1Tags = media_file_tags($rev1Meta);
286        $rev2Tags = media_file_tags($rev2Meta);
287        // FIXME rev2Tags-only stuff ignored
288        foreach ($rev1Tags as $key => $tag) {
289            if ($tag['value'] != $rev2Tags[$key]['value']) {
290                $rev2Tags[$key]['highlighted'] = true;
291                $rev1Tags[$key]['highlighted'] = true;
292            } elseif (!$tag['value'] || !$rev2Tags[$key]['value']) {
293                unset($rev2Tags[$key]);
294                unset($rev1Tags[$key]);
295            }
296        }
297
298        echo '<tr>';
299        foreach ([$rev1Tags, $rev2Tags] as $tags) {
300            echo '<td>';
301
302            echo '<dl class="img_tags">';
303            foreach ($tags as $tag) {
304                $value = cleanText($tag['value']);
305                if (!$value) $value = '-';
306                echo '<dt>' . $lang[$tag['tag'][1]] . '</dt>';
307                echo '<dd>';
308                if (!empty($tag['highlighted'])) echo '<strong>';
309                if ($tag['tag'][2] == 'date') {
310                    echo dformat($value);
311                } else {
312                    echo hsc($value);
313                }
314                if (!empty($tag['highlighted'])) echo '</strong>';
315                echo '</dd>';
316            }
317            echo '</dl>';
318
319            echo '</td>';
320        }
321        echo '</tr>';
322
323        echo '</table>';
324        echo '</div>';
325    }
326}
327