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 dokuwiki\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 71 /** 72 * Prepare revision info of comparison pair 73 */ 74 protected function preProcess() 75 { 76 $changelog =& $this->changelog; 77 78 // revision info of older file (left side) 79 $this->oldRevInfo = $changelog->getRevisionInfo($this->oldRev); 80 // revision info of newer file (right side) 81 $this->newRevInfo = $changelog->getRevisionInfo($this->newRev); 82 83 $this->is_img = preg_match('/\.(jpe?g|gif|png)$/', $this->id); 84 85 foreach ([&$this->oldRevInfo, &$this->newRevInfo] as &$revInfo) { 86 // use timestamp and '' properly as $rev for the current file 87 $isCurrent = $changelog->isCurrentRevision($revInfo['date']); 88 $revInfo += [ 89 'current' => $isCurrent, 90 'rev' => $isCurrent ? '' : $revInfo['date'], 91 ]; 92 93 // headline in the Diff view navigation 94 $revInfo['navTitle'] = $this->revisionTitle($revInfo); 95 96 if ($this->is_img) { 97 $rev = $revInfo['rev']; 98 $meta = new JpegMeta(mediaFN($this->id, $rev)); 99 // get image width and height for the mediamanager preview panel 100 $revInfo['previewSize'] = media_image_preview_size($this->id, $rev, $meta); 101 } 102 } 103 unset($revInfo); 104 105 // re-check image, ensure minimum image width for showImageDiff() 106 $this->is_img = ($this->is_img 107 && ($this->oldRevInfo['previewSize'][0] ?? 0) >= 30 108 && ($this->newRevInfo['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 168 [$oldRev, $newRev] = [(int)$this->oldRevInfo['date'], (int)$this->newRevInfo['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]', $oldRev); 182 $form->setHiddenField('rev2[1]', $newRev); 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 // diff view type: opacity or portions 198 $type = $this->preference['difftype']; 199 200 // use '' for current revision 201 [$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']]; 202 203 // adjust image width, right side (newer) has priority 204 $oldRevSize = $this->oldRevInfo['previewSize']; 205 $newRevSize = $this->newRevInfo['previewSize']; 206 if ($oldRevSize != $newRevSize) { 207 if ($newRevSize[0] > $oldRevSize[0]) { 208 $oldRevSize = $newRevSize; 209 } 210 } 211 212 $oldRevSrc = ml($this->id, ['rev' => $oldRev, 'h' => $oldRevSize[1], 'w' => $oldRevSize[0]]); 213 $newRevSrc = ml($this->id, ['rev' => $newRev, 'h' => $oldRevSize[1], 'w' => $oldRevSize[0]]); 214 215 // slider 216 echo '<div class="slider" style="max-width: '.($oldRevSize[0]-20).'px;" ></div>'; 217 218 // two images in divs 219 echo '<div class="imageDiff '.$type.'">'; 220 echo '<div class="image1" style="max-width: '.$oldRevSize[0].'px;">'; 221 echo '<img src="'.$oldRevSrc.'" alt="" />'; 222 echo '</div>'; 223 echo '<div class="image2" style="max-width: '.$oldRevSize[0].'px;">'; 224 echo '<img src="'.$newRevSrc.'" 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 $changelog =& $this->changelog; 238 239 $ns = getNS($this->id); 240 $auth = auth_quickaclcheck("$ns:*"); 241 242 // use '' for current revision 243 [$oldRev, $newRev] = [$this->oldRevInfo['rev'], $this->newRevInfo['rev']]; 244 245 $oldRevMeta = new JpegMeta(mediaFN($this->id, $oldRev)); 246 $newRevMeta = new JpegMeta(mediaFN($this->id, $newRev)); 247 248 // display diff view table 249 echo '<div class="table">'; 250 echo '<table>'; 251 echo '<tr>'; 252 echo '<th>'. $this->oldRevInfo['navTitle'] .'</th>'; 253 echo '<th>'. $this->newRevInfo['navTitle'] .'</th>'; 254 echo '</tr>'; 255 256 echo '<tr class="image">'; 257 echo '<td>'; 258 media_preview($this->id, $auth, $oldRev, $oldRevMeta); // $auth not used in media_preview()? 259 echo '</td>'; 260 261 echo '<td>'; 262 media_preview($this->id, $auth, $newRev, $newRevMeta); 263 echo '</td>'; 264 echo '</tr>'; 265 266 echo '<tr class="actions">'; 267 echo '<td>'; 268 media_preview_buttons($this->id, $auth, $oldRev); // $auth used in media_preview_buttons() 269 echo '</td>'; 270 271 echo '<td>'; 272 media_preview_buttons($this->id, $auth, $newRev); 273 echo '</td>'; 274 echo '</tr>'; 275 276 $l_tags = media_file_tags($oldRevMeta); 277 $r_tags = media_file_tags($newRevMeta); 278 // FIXME r_tags-only stuff 279 foreach ($l_tags as $key => $l_tag) { 280 if ($l_tag['value'] != $r_tags[$key]['value']) { 281 $r_tags[$key]['highlighted'] = true; 282 $l_tags[$key]['highlighted'] = true; 283 } elseif (!$l_tag['value'] || !$r_tags[$key]['value']) { 284 unset($r_tags[$key]); 285 unset($l_tags[$key]); 286 } 287 } 288 289 echo '<tr>'; 290 foreach (array($l_tags, $r_tags) as $tags) { 291 echo '<td>'; 292 293 echo '<dl class="img_tags">'; 294 foreach ($tags as $tag) { 295 $value = cleanText($tag['value']); 296 if (!$value) $value = '-'; 297 echo '<dt>'.$lang[$tag['tag'][1]].'</dt>'; 298 echo '<dd>'; 299 if ($tag['highlighted']) echo '<strong>'; 300 if ($tag['tag'][2] == 'date') { 301 echo dformat($value); 302 } else { 303 echo hsc($value); 304 } 305 if ($tag['highlighted']) echo '</strong>'; 306 echo '</dd>'; 307 } 308 echo '</dl>'; 309 310 echo '</td>'; 311 } 312 echo '</tr>'; 313 314 echo '</table>'; 315 echo '</div>'; 316 } 317 318 /** 319 * Revision Title for MediaDiff table headline 320 * 321 * @param array $info Revision info structure of a media file 322 * @return string 323 */ 324 protected function revisionTitle(array $info) 325 { 326 global $lang, $INFO; 327 328 if (isset($info['date'])) { 329 $rev = $info['date']; 330 $title = '<bdi><a class="wikilink1" href="'.ml($this->id, ['rev' => $rev]).'">' 331 . dformat($rev).'</a></bdi>'; 332 } else { 333 $rev = false; 334 $title = '—'; 335 } 336 if (isset($info['current']) || ($rev && $rev == $INFO['currentrev'])) { 337 $title .= ' ('.$lang['current'].')'; 338 } 339 340 // append separator 341 $title .= ($this->preference['difftype'] === 'inline') ? ' ' : '<br />'; 342 343 // supplement 344 if (isset($info['date'])) { 345 $objRevInfo = (new MediaRevisions($this->id))->getObjRevInfo($info); 346 $title .= $objRevInfo->editSummary().' '.$objRevInfo->editor(); 347 } 348 return $title; 349 } 350 351} 352