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        $changelogRev1['media'] = $changelogRev2['media'] = true;
86
87        $this->RevInfo1 = new RevisionInfo($changelogRev1);
88        $this->RevInfo2 = new RevisionInfo($changelogRev2);
89
90        $this->is_img = preg_match('/\.(jpe?g|gif|png)$/', $this->id);
91
92        foreach ([$this->RevInfo1, $this->RevInfo2] as $RevInfo) {
93            $isCurrent = $changelog->isCurrentRevision($RevInfo->val('date'));
94            $RevInfo->isCurrent($isCurrent);
95
96            if ($this->is_img) {
97                $rev = $isCurrent ? '' : $RevInfo->val('date');
98                $meta = new JpegMeta(mediaFN($this->id, $rev));
99                // get image width and height for the media manager preview panel
100                $RevInfo->append([
101                    'previewSize' => media_image_preview_size($this->id, $rev, $meta)
102                ]);
103            }
104        }
105
106        // re-check image, ensure minimum image width for showImageDiff()
107        $this->is_img = ($this->is_img
108            && ($this->RevInfo1->val('previewSize')[0] ?? 0) >= 30
109            && ($this->RevInfo2->val('previewSize')[0] ?? 0) >= 30
110        );
111        // adjust requested diff view type
112        if (!$this->is_img) {
113            $this->preference['difftype'] = 'both';
114        }
115    }
116
117
118    /**
119     * Shows difference between two revisions of media
120     *
121     * @author Kate Arzamastseva <pshns@ukr.net>
122     */
123    public function show()
124    {
125        global $conf;
126
127        $ns = getNS($this->id);
128        $auth = auth_quickaclcheck("$ns:*");
129
130        if ($auth < AUTH_READ || !$this->id || !$conf['mediarevisions']) return;
131
132        // retrieve form parameters: rev, rev2, difftype
133        $this->handle();
134        // prepare revision info of comparison pair
135        $this->preProcess();
136
137        // display intro
138        if ($this->preference['showIntro']) echo p_locale_xhtml('diff');
139
140        // print form to choose diff view type
141        if ($this->is_img && !$this->preference['fromAjax']) {
142            $this->showDiffViewSelector();
143            echo '<div id="mediamanager__diff" >';
144        }
145
146        switch ($this->preference['difftype']) {
147            case 'opacity':
148            case 'portions':
149                $this->showImageDiff();
150                break;
151            case 'both':
152            default:
153                $this->showFileDiff();
154                break;
155        }
156
157        if ($this->is_img && !$this->preference['fromAjax']) {
158            echo '</div>';
159        }
160    }
161
162    /**
163     * Print form to choose diff view type
164     * the dropdown is to be added through JavaScript, see lib/scripts/media.js
165     */
166    protected function showDiffViewSelector()
167    {
168        // use timestamp for current revision, date may be false when revisions < 2
169        [$rev1, $rev2] = [(int)$this->RevInfo1->val('date'), (int)$this->RevInfo2->val('date')];
170
171        echo '<div class="diffoptions group">';
172
173        $form = new Form([
174            'id' => 'mediamanager__form_diffview',
175            'action' => media_managerURL([], '&'),
176            'method' => 'get',
177            'class' => 'diffView',
178        ]);
179        $form->addTagOpen('div')->addClass('no');
180        $form->setHiddenField('sectok', null);
181        $form->setHiddenField('mediado', 'diff');
182        $form->setHiddenField('rev2[0]', $rev1);
183        $form->setHiddenField('rev2[1]', $rev2);
184        $form->addTagClose('div');
185        echo $form->toHTML();
186
187        echo '</div>'; // .diffoptions
188    }
189
190    /**
191     * Prints two images side by side
192     * and slider
193     *
194     * @author Kate Arzamastseva <pshns@ukr.net>
195     */
196    protected function showImageDiff()
197    {
198        $rev1 = $this->RevInfo1->isCurrent() ? '' : $this->RevInfo1->val('date');
199        $rev2 = $this->RevInfo2->isCurrent() ? '' : $this->RevInfo2->val('date');
200
201        // diff view type: opacity or portions
202        $type = $this->preference['difftype'];
203
204        // adjust image width, right side (newer) has priority
205        $rev1Size = $this->RevInfo1->val('previewSize');
206        $rev2Size = $this->RevInfo2->val('previewSize');
207        if ($rev1Size != $rev2Size) {
208            if ($rev2Size[0] > $rev1Size[0]) {
209                $rev1Size = $rev2Size;
210            }
211        }
212
213        $rev1Src = ml($this->id, ['rev' => $rev1, 'h' => $rev1Size[1], 'w' => $rev1Size[0]]);
214        $rev2Src = ml($this->id, ['rev' => $rev2, 'h' => $rev1Size[1], 'w' => $rev1Size[0]]);
215
216        // slider
217        echo '<div class="slider" style="max-width: '.($rev1Size[0]-20).'px;" ></div>';
218
219        // two images in divs
220        echo '<div class="imageDiff '.$type.'">';
221        echo '<div class="image1" style="max-width: '.$rev1Size[0].'px;">';
222        echo '<img src="'.$rev1Src.'" alt="" />';
223        echo '</div>';
224        echo '<div class="image2" style="max-width: '.$rev1Size[0].'px;">';
225        echo '<img src="'.$rev2Src.'" alt="" />';
226        echo '</div>';
227        echo '</div>';
228    }
229
230    /**
231     * Shows difference between two revisions of media file
232     *
233     * @author Kate Arzamastseva <pshns@ukr.net>
234     */
235    protected function showFileDiff()
236    {
237        global $lang;
238
239        $ns = getNS($this->id);
240        $auth = auth_quickaclcheck("$ns:*");
241
242        $rev1 = $this->RevInfo1->isCurrent() ? '' : (int)$this->RevInfo1->val('date');
243        $rev2 = $this->RevInfo2->isCurrent() ? '' : (int)$this->RevInfo2->val('date');
244
245        // revision title
246        $rev1Title = trim($this->RevInfo1->showRevisionTitle() .' '. $this->RevInfo1->showCurrentIndicator());
247        $rev1Summary = ($this->RevInfo1->val('date'))
248            ? $this->RevInfo1->showEditSummary() .' '. $this->RevInfo1->showEditor()
249            : '';
250        $rev2Title = trim($this->RevInfo2->showRevisionTitle() .' '. $this->RevInfo2->showCurrentIndicator());
251        $rev2Summary = ($this->RevInfo2->val('date'))
252            ? $this->RevInfo2->showEditSummary() .' '. $this->RevInfo2->showEditor()
253            : '';
254
255        $rev1Meta = new JpegMeta(mediaFN($this->id, $rev1));
256        $rev2Meta = new JpegMeta(mediaFN($this->id, $rev2));
257
258        // display diff view table
259        echo '<div class="table">';
260        echo '<table>';
261        echo '<tr>';
262        echo '<th>'. $rev1Title .' '. $rev1Summary .'</th>';
263        echo '<th>'. $rev2Title .' '. $rev2Summary .'</th>';
264        echo '</tr>';
265
266        echo '<tr class="image">';
267        echo '<td>';
268        media_preview($this->id, $auth, $rev1, $rev1Meta); // $auth not used in media_preview()?
269        echo '</td>';
270
271        echo '<td>';
272        media_preview($this->id, $auth, $rev2, $rev2Meta);
273        echo '</td>';
274        echo '</tr>';
275
276        echo '<tr class="actions">';
277        echo '<td>';
278        media_preview_buttons($this->id, $auth, $rev1); // $auth used in media_preview_buttons()
279        echo '</td>';
280
281        echo '<td>';
282        media_preview_buttons($this->id, $auth, $rev2);
283        echo '</td>';
284        echo '</tr>';
285
286        $rev1Tags = media_file_tags($rev1Meta);
287        $rev2Tags = media_file_tags($rev2Meta);
288        // FIXME rev2Tags-only stuff ignored
289        foreach ($rev1Tags as $key => $tag) {
290            if ($tag['value'] != $rev2Tags[$key]['value']) {
291                $rev2Tags[$key]['highlighted'] = true;
292                $rev1Tags[$key]['highlighted'] = true;
293            } elseif (!$tag['value'] || !$rev2Tags[$key]['value']) {
294                unset($rev2Tags[$key]);
295                unset($rev1Tags[$key]);
296            }
297        }
298
299        echo '<tr>';
300        foreach ([$rev1Tags, $rev2Tags] as $tags) {
301            echo '<td>';
302
303            echo '<dl class="img_tags">';
304            foreach ($tags as $tag) {
305                $value = cleanText($tag['value']);
306                if (!$value) $value = '-';
307                echo '<dt>'.$lang[$tag['tag'][1]].'</dt>';
308                echo '<dd>';
309                if (!empty($tag['highlighted'])) echo '<strong>';
310                if ($tag['tag'][2] == 'date') {
311                    echo dformat($value);
312                } else {
313                    echo hsc($value);
314                }
315                if (!empty($tag['highlighted'])) echo '</strong>';
316                echo '</dd>';
317            }
318            echo '</dl>';
319
320            echo '</td>';
321        }
322        echo '</tr>';
323
324        echo '</table>';
325        echo '</div>';
326    }
327
328}
329