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