1<?php 2 3namespace dokuwiki\Ui; 4 5use dokuwiki\ChangeLog\PageChangeLog; 6use dokuwiki\ChangeLog\MediaChangeLog; 7use dokuwiki\Extension\Event; 8use dokuwiki\Form\Form; 9 10/** 11 * DokuWiki Revisions Interface 12 * 13 * @package dokuwiki\Ui 14 */ 15class Revisions extends Ui 16{ 17 protected $first; 18 protected $media_id; 19 20 /** 21 * Revisions Ui constructor 22 * 23 * @param int $first skip the first n changelog lines 24 * @param bool|string $media_id id of media, or false for current page 25 */ 26 public function __construct($first = 0, $media_id = false) 27 { 28 $this->first = $first; 29 $this->media_id = $media_id; 30 } 31 32 /** 33 * Display list of old revisions 34 * 35 * @author Andreas Gohr <andi@splitbrain.org> 36 * @author Ben Coburn <btcoburn@silicodon.net> 37 * @author Kate Arzamastseva <pshns@ukr.net> 38 * @author Satoshi Sahara <sahara.satoshi@gmail.com> 39 * 40 * @triggers HTML_REVISIONSFORM_OUTPUT 41 * @return void 42 */ 43 public function show() 44 { 45 global $ID; 46 47 if ($this->media_id) { 48 return $this->showMediaRevisions($this->media_id); 49 } else { 50 return $this->showPageRevisions($ID); 51 } 52 } 53 54 /** 55 * Display a list of Media Revisions in the MediaManager 56 * 57 * @param string $id media id 58 * @return void 59 */ 60 protected function showMediaRevisions($id) 61 { 62 global $lang; 63 64 // get revisions, and set correct pagenation parameters (first, hasNext) 65 $first = $this->first; 66 $hasNext = false; 67 $revisions = $this->getRevisions($first, $hasNext); 68 69 // create the form 70 $form = new Form([ 71 'id' => 'page__revisions', // must not be "media__revisions" 72 'action' => media_managerURL(['image' => $id], '&'), 73 'class' => 'changes', 74 ]); 75 $form->setHiddenField('mediado', 'diff'); // required for media revisions 76 $form->addTagOpen('div')->addClass('no'); 77 78 // start listing 79 $form->addTagOpen('ul'); 80 foreach ($revisions as $info) { 81 $rev = $info['date']; 82 $class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : ''; 83 $form->addTagOpen('li')->addClass($class); 84 $form->addTagOpen('div')->addClass('li'); 85 86 if (isset($info['current'])) { 87 $form->addCheckbox('rev2[]')->val('current'); 88 } elseif (file_exists(mediaFN($id, $rev))) { 89 $form->addCheckbox('rev2[]')->val($rev); 90 } else { 91 $form->addCheckbox('')->val($rev)->attr('disabled','disabled'); 92 } 93 $form->addHTML(' '); 94 95 $objRevInfo = $this->getObjRevInfo($info); 96 $html = implode(' ', [ 97 $objRevInfo->editDate(), // edit date and time 98 $objRevInfo->difflink(), // link to diffview icon 99 $objRevInfo->itemName(), // name of page or media 100 '<div>', 101 $objRevInfo->editSummary(), // edit summary 102 $objRevInfo->editor(), // editor info 103 html_sizechange($info['sizechange']), // size change indicator 104 $objRevInfo->currentIndicator(), // current indicator (only when k=1) 105 '</div>', 106 ]); 107 $form->addHTML($html); 108 109 $form->addTagClose('div'); 110 $form->addTagClose('li'); 111 } 112 $form->addTagClose('ul'); // end of revision list 113 114 // show button for diff view 115 $form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit'); 116 117 $form->addTagClose('div'); // close div class=no 118 119 // emit HTML_REVISIONSFORM_OUTPUT event 120 Event::createAndTrigger('HTML_REVISIONSFORM_OUTPUT', $form, null, false); 121 print $form->toHTML(); 122 123 // provide navigation for pagenated revision list (of pages and/or media files) 124 print $this->htmlNavigation($id, $first, $hasNext); 125 } 126 127 /** 128 * Display a list of Page Revisions 129 * 130 * @return void 131 */ 132 protected function showPageRevisions($id) 133 { 134 global $lang; 135 136 // get revisions, and set correct pagenation parameters (first, hasNext) 137 $first = $this->first; 138 $hasNext = false; 139 $revisions = $this->getRevisions($first, $hasNext); 140 141 // print intro 142 print p_locale_xhtml('revisions'); 143 144 // create the form 145 $form = new Form([ 146 'id' => 'page__revisions', 147 'class' => 'changes', 148 ]); 149 $form->addTagOpen('div')->addClass('no'); 150 151 // start listing 152 $form->addTagOpen('ul'); 153 foreach ($revisions as $info) { 154 $rev = $info['date']; 155 $class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : ''; 156 $form->addTagOpen('li')->addClass($class); 157 $form->addTagOpen('div')->addClass('li'); 158 159 if (page_exists($id, $rev)) { 160 $form->addCheckbox('rev2[]')->val($rev); 161 } else { 162 $form->addCheckbox('')->val($rev)->attr('disabled','disabled'); 163 } 164 $form->addHTML(' '); 165 166 $objRevInfo = $this->getObjRevInfo($info); 167 $html = implode(' ', [ 168 $objRevInfo->editDate(), // edit date and time 169 $objRevInfo->difflink(), // link to diffview icon 170 $objRevInfo->itemName(), // name of page or media 171 $objRevInfo->editSummary(), // edit summary 172 $objRevInfo->editor(), // editor info 173 html_sizechange($info['sizechange']), // size change indicator 174 $objRevInfo->currentIndicator(), // current indicator (only when k=1) 175 ]); 176 $form->addHTML($html); 177 $form->addTagClose('div'); 178 $form->addTagClose('li'); 179 } 180 $form->addTagClose('ul'); // end of revision list 181 182 // show button for diff view 183 $form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit'); 184 185 $form->addTagClose('div'); // close div class=no 186 187 // emit HTML_REVISIONSFORM_OUTPUT event 188 Event::createAndTrigger('HTML_REVISIONSFORM_OUTPUT', $form, null, false); 189 print $form->toHTML(); 190 191 // provide navigation for pagenated revision list (of pages and/or media files) 192 print $this->htmlNavigation($id, $first, $hasNext); 193 } 194 195 196 /** 197 * Get revisions, and set correct pagenation parameters (first, hasNext) 198 * 199 * @param int $first 200 * @param bool $hasNext 201 * @return array revisions to be shown in a pagenated list 202 */ 203 protected function getRevisions(&$first, &$hasNext) 204 { 205 global $INFO, $conf; 206 207 if ($this->media_id) { 208 $changelog = new MediaChangeLog($this->media_id); 209 } else { 210 $changelog = new PageChangeLog($INFO['id']); 211 } 212 213 $revisions = []; 214 215 /* we need to get one additional log entry to be able to 216 * decide if this is the last page or is there another one. 217 * see also Ui\Recent::getRecents() 218 */ 219 $revlist = $changelog->getRevisions($first, $conf['recent'] +1); 220 if (count($revlist) == 0 && $first != 0) { 221 $first = 0; 222 $revlist = $changelog->getRevisions($first, $conf['recent'] +1); 223 } 224 $exists = ($this->media_id) ? file_exists(mediaFN($this->media_id)) : $INFO['exists']; 225 if ($first === 0 && $exists) { 226 // add current page or media as revision[0] 227 if ($this->media_id) { 228 $rev = filemtime(fullpath(mediaFN($this->media_id))); 229 $revisions[] = $changelog->getRevisionInfo($rev) + array( 230 'media' => true, 231 'current' => true, 232 ); 233 } else { 234 $revisions[] = array( 235 'date' => $INFO['lastmod'], 236 'ip' => null, 237 'type' => $INFO['meta']['last_change']['type'], 238 'id' => $INFO['id'], 239 'user' => $INFO['editor'], 240 'sum' => $INFO['sum'], 241 'extra' => null, 242 'sizechange' => $INFO['meta']['last_change']['sizechange'], 243 'current' => true, 244 ); 245 } 246 } 247 248 // decide if this is the last page or is there another one 249 $hasNext = false; 250 if (count($revlist) > $conf['recent']) { 251 $hasNext = true; 252 array_pop($revlist); // remove one additional log entry 253 } 254 255 // append each revison info array to the revisions 256 foreach ($revlist as $rev) { 257 if ($this->media_id) { 258 $revisions[] = $changelog->getRevisionInfo($rev) + array('media' => true); 259 } else { 260 $revisions[] = $changelog->getRevisionInfo($rev); 261 } 262 } 263 return $revisions; 264 } 265 266 /** 267 * Navigation buttons for Pagenation (prev/next) 268 * 269 * @param string $id page id or media id 270 * @param int $first 271 * @param bool $hasNext 272 * @return array html 273 */ 274 protected function htmlNavigation($id, $first, $hasNext) 275 { 276 global $conf; 277 278 $html = '<div class="pagenav">'; 279 $last = $first + $conf['recent']; 280 if ($first > 0) { 281 $first = max($first - $conf['recent'], 0); 282 $html.= '<div class="pagenav-prev">'; 283 if ($this->media_id) { 284 $html.= html_btn('newer', $id, "p", media_managerURL(['first' => $first], '&', false, true)); 285 } else { 286 $html.= html_btn('newer', $id, "p" ,['do' => 'revisions', 'first' => $first]); 287 } 288 $html.= '</div>'; 289 } 290 if ($hasNext) { 291 $html.= '<div class="pagenav-next">'; 292 if ($this->media_id) { 293 $html.= html_btn('older', $id, "n", media_managerURL(['first' => $last], '&', false, true)); 294 } else { 295 $html.= html_btn('older', $id, "n", ['do' => 'revisions', 'first' => $last]); 296 } 297 $html.= '</div>'; 298 } 299 $html.= '</div>'; 300 return $html; 301 } 302 303 /** 304 * Returns instance of objRevInfo 305 * 306 * @param array $info Revision info structure of a page or media file 307 * @return objRevInfo object (anonymous class) 308 */ 309 protected function getObjRevInfo(array $info) 310 { 311 return new class ($info) // anonymous class (objRevInfo) 312 { 313 protected $info; 314 315 public function __construct(array $info) 316 { 317 $this->info = $info; 318 } 319 320 // current indicator 321 public function currentIndicator() 322 { 323 global $lang; 324 return ($this->info['current']) ? '('.$lang['current'].')' : ''; 325 } 326 327 // edit date and time of the page or media file 328 public function editDate() 329 { 330 return '<span class="date">'. dformat($this->info['date']) .'</span>'; 331 } 332 333 // edit summary 334 public function editSummary() 335 { 336 return '<span class="sum">'.' – '. hsc($this->info['sum']).'</span>'; 337 } 338 339 // editor of the page or media file 340 public function editor() 341 { 342 // slightly different with display of Ui\Recent, i.e. external edit 343 global $lang; 344 $html = '<span class="user">'; 345 if (!$this->info['user'] && !$this->info['ip']) { 346 $html.= '('.$lang['external_edit'].')'; 347 } elseif ($this->info['user']) { 348 $html.= '<bdi>'. editorinfo($this->info['user']) .'</bdi>'; 349 if (auth_ismanager()) $html.= ' <bdo dir="ltr">('. $this->info['ip'] .')</bdo>'; 350 } else { 351 $html.= '<bdo dir="ltr">'. $this->info['ip'] .'</bdo>'; 352 } 353 $html.= '</span>'; 354 return $html; 355 } 356 357 // name of the page or media file 358 public function itemName() 359 { 360 // slightly different with display of Ui\Recent, i.e. revison may not exists 361 $id = $this->info['id']; 362 $rev = $this->info['date']; 363 364 if (isset($this->info['media'])) { 365 // media file revision 366 if (isset($this->info['current'])) { 367 $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view'], '&'); 368 $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>'; 369 } elseif (file_exists(mediaFN($id, $rev))) { 370 $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view', 'rev'=> $rev], '&'); 371 $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>'; 372 } else { 373 $html = $id; 374 } 375 return $html; 376 } else { 377 // page revision 378 $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id; 379 if (!$display_name) $display_name = $id; 380 if ($this->info['current'] || page_exists($id, $rev)) { 381 $href = wl($id, "rev=$rev", false, '&'); 382 $html = '<a href="'.$href.'" class="wikilink1">'.$display_name.'</a>'; 383 } else { 384 $html = $display_name; 385 } 386 return $html; 387 } 388 } 389 390 // icon difflink 391 public function difflink() 392 { 393 global $lang; 394 $id = $this->info['id']; 395 $rev = $this->info['date']; 396 397 if (isset($this->info['media'])) { 398 // media file revision 399 if (isset($this->info['current']) || !file_exists(mediaFN($id, $rev))) { 400 $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'; 401 } else { 402 $href = media_managerURL(['image'=> $id, 'rev'=> $rev, 'mediado'=>'diff'], '&'); 403 $html = '<a href="'.$href.'" class="diff_link">' 404 . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"' 405 . ' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />' 406 . '</a> '; 407 } 408 return $html; 409 } else { 410 // page revision 411 if ($this->info['current'] || !page_exists($id, $rev)) { 412 $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'; 413 } else { 414 $href = wl($id, "rev=$rev,do=diff", false, '&'); 415 $html = '<a href="'.$href.'" class="diff_link">' 416 . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"' 417 . ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />' 418 . '</a>'; 419 } 420 return $html; 421 } 422 } 423 424 // size change 425 public function sizeChange() 426 { 427 $class = 'sizechange'; 428 $value = filesize_h(abs($this->info['sizechange'])); 429 if ($this->info['sizechange'] > 0) { 430 $class .= ' positive'; 431 $value = '+' . $value; 432 } elseif ($this->info['sizechange'] < 0) { 433 $class .= ' negative'; 434 $value = '-' . $value; 435 } else { 436 $value = '±' . $value; 437 } 438 return '<span class="'.$class.'">'.$value.'</span>'; 439 } 440 }; // end of anonymous class (objRevInfo) 441 } 442 443} 444