xref: /dokuwiki/inc/Ui/MediaDiff.php (revision dd9e8e5ea54469964faab99223a61bd48146ac42)
1<?php
2
3namespace dokuwiki\Ui;
4
5use dokuwiki\ChangeLog\MediaChangeLog;
6use dokuwiki\ChangeLog\RevisionInfo;
7use dokuwiki\File\MediaFile;
8use dokuwiki\Form\Form;
9use dokuwiki\Ui\Media\Display;
10use InvalidArgumentException;
11use JpegMeta;
12
13/**
14 * DokuWiki MediaDiff Interface
15 *
16 * @package dokuwiki\Ui
17 */
18class MediaDiff extends Diff
19{
20    /* @var MediaChangeLog */
21    protected $changelog;
22
23    /* @var RevisionInfo older revision */
24    protected $RevInfo1;
25    /* @var RevisionInfo newer revision */
26    protected $RevInfo2;
27
28    /* @var bool */
29    protected $is_img;
30
31    /**
32     * MediaDiff Ui constructor
33     *
34     * @param string $id media id
35     */
36    public function __construct($id)
37    {
38        if (!isset($id)) {
39            throw new InvalidArgumentException('media id should not be empty!');
40        }
41
42        // init preference
43        $this->preference['fromAjax'] = false;  // see dokuwiki\Ajax::callMediadiff()
44        $this->preference['showIntro'] = false;
45        $this->preference['difftype'] = 'both'; // diff view type: both, opacity or portions
46
47        parent::__construct($id);
48    }
49
50    /** @inheritdoc */
51    protected function setChangeLog()
52    {
53        $this->changelog = new MediaChangeLog($this->id);
54    }
55
56    /**
57     * Handle requested revision(s) and diff view preferences
58     *
59     * @return void
60     */
61    protected function handle()
62    {
63        global $INPUT;
64
65        // retrieve requested rev or rev2
66        parent::handle();
67
68        // requested diff view type
69        if ($INPUT->has('difftype')) {
70            $this->preference['difftype'] = $INPUT->str('difftype');
71        }
72    }
73
74    /**
75     * Prepare revision info of comparison pair
76     */
77    protected function preProcess()
78    {
79        $changelog =& $this->changelog;
80
81        // create revision info object for older and newer sides
82        // RevInfo1 : older, left side
83        // RevInfo2 : newer, right side
84
85        $changelogRev1 = $changelog->getRevisionInfo($this->rev1);
86        $changelogRev2 = $changelog->getRevisionInfo($this->rev2);
87
88        $this->RevInfo1 = new RevisionInfo($changelogRev1);
89        $this->RevInfo2 = new RevisionInfo($changelogRev2);
90
91        $this->is_img = preg_match('/\.(jpe?g|gif|png)$/', $this->id);
92
93        foreach ([$this->RevInfo1, $this->RevInfo2] as $RevInfo) {
94            $isCurrent = $changelog->isCurrentRevision($RevInfo->val('date'));
95            $RevInfo->isCurrent($isCurrent);
96
97            if ($this->is_img) {
98                $rev = $isCurrent ? '' : $RevInfo->val('date');
99                // get display dimensions for the media manager preview panel
100                $RevInfo->append([
101                    'previewSize' => (new MediaFile($this->id, $rev))->getDisplayDimensions(500, 500, false)
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        match ($this->preference['difftype']) {
147            'opacity', 'portions' => $this->showImageDiff(),
148            default => $this->showFileDiff(),
149        };
150
151        if ($this->is_img && !$this->preference['fromAjax']) {
152            echo '</div>';
153        }
154    }
155
156    /**
157     * Print form to choose diff view type
158     * the dropdown is to be added through JavaScript, see lib/scripts/media.js
159     */
160    protected function showDiffViewSelector()
161    {
162        // use timestamp for current revision, date may be false when revisions < 2
163        [$rev1, $rev2] = [(int)$this->RevInfo1->val('date'), (int)$this->RevInfo2->val('date')];
164
165        echo '<div class="diffoptions group">';
166
167        $form = new Form([
168            'id' => 'mediamanager__form_diffview',
169            'action' => media_managerURL([], '&'),
170            'method' => 'get',
171            'class' => 'diffView',
172        ]);
173        $form->addTagOpen('div')->addClass('no');
174        $form->setHiddenField('sectok', null);
175        $form->setHiddenField('mediado', 'diff');
176        $form->setHiddenField('rev2[0]', $rev1);
177        $form->setHiddenField('rev2[1]', $rev2);
178        $form->addTagClose('div');
179        echo $form->toHTML();
180
181        echo '</div>'; // .diffoptions
182    }
183
184    /**
185     * Prints two images side by side
186     * and slider
187     *
188     * @author Kate Arzamastseva <pshns@ukr.net>
189     */
190    protected function showImageDiff()
191    {
192        $rev1 = $this->RevInfo1->isCurrent() ? '' : $this->RevInfo1->val('date');
193        $rev2 = $this->RevInfo2->isCurrent() ? '' : $this->RevInfo2->val('date');
194
195        // diff view type: opacity or portions
196        $type = $this->preference['difftype'];
197
198        // adjust image width, right side (newer) has priority
199        $rev1Size = $this->RevInfo1->val('previewSize');
200        $rev2Size = $this->RevInfo2->val('previewSize');
201        if ($rev1Size != $rev2Size) {
202            if ($rev2Size[0] > $rev1Size[0]) {
203                $rev1Size = $rev2Size;
204            }
205        }
206
207        $rev1Src = ml($this->id, ['rev' => $rev1, 'h' => $rev1Size[1], 'w' => $rev1Size[0], 'fit' => 1]);
208        $rev2Src = ml($this->id, ['rev' => $rev2, 'h' => $rev1Size[1], 'w' => $rev1Size[0], 'fit' => 1]);
209
210        // slider
211        echo '<div class="slider" style="max-width: ' . ($rev1Size[0] - 20) . 'px;" ></div>';
212
213        // two images in divs
214        echo '<div class="imageDiff ' . $type . '">';
215        echo '<div class="image1" style="max-width: ' . $rev1Size[0] . 'px;">';
216        echo '<img src="' . $rev1Src . '" alt="" />';
217        echo '</div>';
218        echo '<div class="image2" style="max-width: ' . $rev1Size[0] . 'px;">';
219        echo '<img src="' . $rev2Src . '" alt="" />';
220        echo '</div>';
221        echo '</div>';
222    }
223
224    /**
225     * Shows difference between two revisions of media file
226     *
227     * @author Kate Arzamastseva <pshns@ukr.net>
228     */
229    protected function showFileDiff()
230    {
231        global $lang;
232
233        $ns = getNS($this->id);
234        $auth = auth_quickaclcheck("$ns:*");
235
236        $rev1 = $this->RevInfo1->isCurrent() ? '' : (int)$this->RevInfo1->val('date');
237        $rev2 = $this->RevInfo2->isCurrent() ? '' : (int)$this->RevInfo2->val('date');
238
239        // revision title
240        $rev1Title = trim($this->RevInfo1->showRevisionTitle() . ' ' . $this->RevInfo1->showCurrentIndicator());
241        $rev1Summary = ($this->RevInfo1->val('date'))
242            ? $this->RevInfo1->showEditSummary() . ' ' . $this->RevInfo1->showEditor()
243            : '';
244        $rev2Title = trim($this->RevInfo2->showRevisionTitle() . ' ' . $this->RevInfo2->showCurrentIndicator());
245        $rev2Summary = ($this->RevInfo2->val('date'))
246            ? $this->RevInfo2->showEditSummary() . ' ' . $this->RevInfo2->showEditor()
247            : '';
248
249        $rev1Meta = new JpegMeta(mediaFN($this->id, $rev1));
250        $rev2Meta = new JpegMeta(mediaFN($this->id, $rev2));
251
252        // display diff view table
253        echo '<div class="table">';
254        echo '<table>';
255        echo '<tr>';
256        echo '<th>' . $rev1Title . ' ' . $rev1Summary . '</th>';
257        echo '<th>' . $rev2Title . ' ' . $rev2Summary . '</th>';
258        echo '</tr>';
259
260        echo '<tr class="image">';
261        echo '<td>';
262        echo (new Display(new MediaFile($this->id, $rev1)))->getDetailHtml();
263        echo '</td>';
264
265        echo '<td>';
266        echo (new Display(new MediaFile($this->id, $rev2)))->getDetailHtml();
267        echo '</td>';
268        echo '</tr>';
269
270        echo '<tr class="actions">';
271        echo '<td>';
272        media_preview_buttons($this->id, $auth, $rev1);
273        echo '</td>';
274
275        echo '<td>';
276        media_preview_buttons($this->id, $auth, $rev2);
277        echo '</td>';
278        echo '</tr>';
279
280        $rev1Tags = media_file_tags($rev1Meta);
281        $rev2Tags = media_file_tags($rev2Meta);
282        // FIXME rev2Tags-only stuff ignored
283        foreach ($rev1Tags as $key => $tag) {
284            if ($tag['value'] != $rev2Tags[$key]['value']) {
285                $rev2Tags[$key]['highlighted'] = true;
286                $rev1Tags[$key]['highlighted'] = true;
287            } elseif (!$tag['value'] || !$rev2Tags[$key]['value']) {
288                unset($rev2Tags[$key]);
289                unset($rev1Tags[$key]);
290            }
291        }
292
293        echo '<tr>';
294        foreach ([$rev1Tags, $rev2Tags] as $tags) {
295            echo '<td>';
296
297            echo '<dl class="img_tags">';
298            foreach ($tags as $tag) {
299                $value = cleanText($tag['value']);
300                if (!$value) $value = '-';
301                echo '<dt>' . $lang[$tag['tag'][1]] . '</dt>';
302                echo '<dd>';
303                if (!empty($tag['highlighted'])) echo '<strong>';
304                if ($tag['tag'][2] == 'date') {
305                    echo dformat($value);
306                } else {
307                    echo hsc($value);
308                }
309                if (!empty($tag['highlighted'])) echo '</strong>';
310                echo '</dd>';
311            }
312            echo '</dl>';
313
314            echo '</td>';
315        }
316        echo '</tr>';
317
318        echo '</table>';
319        echo '</div>';
320    }
321}
322