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 HTMLFORM_REVISIONS_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 // print form that might be modified by HTMLFORM_REVISIONS_OUTPUT event handlers 120 print $form->toHTML('revisions'); 121 122 // provide navigation for pagenated revision list (of pages and/or media files) 123 print $this->htmlNavigation($id, $first, $hasNext); 124 } 125 126 /** 127 * Display a list of Page Revisions 128 * 129 * @return void 130 */ 131 protected function showPageRevisions($id) 132 { 133 global $lang; 134 135 // get revisions, and set correct pagenation parameters (first, hasNext) 136 $first = $this->first; 137 $hasNext = false; 138 $revisions = $this->getRevisions($first, $hasNext); 139 140 // print intro 141 print p_locale_xhtml('revisions'); 142 143 // create the form 144 $form = new Form([ 145 'id' => 'page__revisions', 146 'class' => 'changes', 147 ]); 148 $form->addTagOpen('div')->addClass('no'); 149 150 // start listing 151 $form->addTagOpen('ul'); 152 foreach ($revisions as $info) { 153 $rev = $info['date']; 154 $class = ($info['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) ? 'minor' : ''; 155 $form->addTagOpen('li')->addClass($class); 156 $form->addTagOpen('div')->addClass('li'); 157 158 if (page_exists($id, $rev)) { 159 $form->addCheckbox('rev2[]')->val($rev); 160 } else { 161 $form->addCheckbox('')->val($rev)->attr('disabled','disabled'); 162 } 163 $form->addHTML(' '); 164 165 $objRevInfo = $this->getObjRevInfo($info); 166 $html = implode(' ', [ 167 $objRevInfo->editDate(), // edit date and time 168 $objRevInfo->difflink(), // link to diffview icon 169 $objRevInfo->itemName(), // name of page or media 170 $objRevInfo->editSummary(), // edit summary 171 $objRevInfo->editor(), // editor info 172 html_sizechange($info['sizechange']), // size change indicator 173 $objRevInfo->currentIndicator(), // current indicator (only when k=1) 174 ]); 175 $form->addHTML($html); 176 $form->addTagClose('div'); 177 $form->addTagClose('li'); 178 } 179 $form->addTagClose('ul'); // end of revision list 180 181 // show button for diff view 182 $form->addButton('do[diff]', $lang['diff2'])->attr('type', 'submit'); 183 184 $form->addTagClose('div'); // close div class=no 185 186 // print form that might be modified by HTMLFORM_REVISIONS_OUTPUT event handlers 187 print $form->toHTML('revisions'); 188 189 // provide navigation for pagenated revision list (of pages and/or media files) 190 print $this->htmlNavigation($id, $first, $hasNext); 191 } 192 193 194 /** 195 * Get revisions, and set correct pagenation parameters (first, hasNext) 196 * 197 * @param int $first 198 * @param bool $hasNext 199 * @return array revisions to be shown in a pagenated list 200 */ 201 protected function getRevisions(&$first, &$hasNext) 202 { 203 global $INFO, $conf; 204 205 if ($this->media_id) { 206 $changelog = new MediaChangeLog($this->media_id); 207 } else { 208 $changelog = new PageChangeLog($INFO['id']); 209 } 210 211 $revisions = []; 212 213 /* we need to get one additional log entry to be able to 214 * decide if this is the last page or is there another one. 215 * see also Ui\Recent::getRecents() 216 */ 217 $revlist = $changelog->getRevisions($first, $conf['recent'] +1); 218 if (count($revlist) == 0 && $first != 0) { 219 $first = 0; 220 $revlist = $changelog->getRevisions($first, $conf['recent'] +1); 221 } 222 $exists = ($this->media_id) ? file_exists(mediaFN($this->media_id)) : $INFO['exists']; 223 if ($first === 0 && $exists) { 224 // add current page or media as revision[0] 225 if ($this->media_id) { 226 $rev = filemtime(fullpath(mediaFN($this->media_id))); 227 $revisions[] = $changelog->getRevisionInfo($rev) + array( 228 'media' => true, 229 'current' => true, 230 ); 231 } else { 232 $revisions[] = array( 233 'date' => $INFO['lastmod'], 234 'ip' => null, 235 'type' => $INFO['meta']['last_change']['type'], 236 'id' => $INFO['id'], 237 'user' => $INFO['editor'], 238 'sum' => $INFO['sum'], 239 'extra' => null, 240 'sizechange' => $INFO['meta']['last_change']['sizechange'], 241 'current' => true, 242 ); 243 } 244 } 245 246 // decide if this is the last page or is there another one 247 $hasNext = false; 248 if (count($revlist) > $conf['recent']) { 249 $hasNext = true; 250 array_pop($revlist); // remove one additional log entry 251 } 252 253 // append each revison info array to the revisions 254 foreach ($revlist as $rev) { 255 if ($this->media_id) { 256 $revisions[] = $changelog->getRevisionInfo($rev) + array('media' => true); 257 } else { 258 $revisions[] = $changelog->getRevisionInfo($rev); 259 } 260 } 261 return $revisions; 262 } 263 264 /** 265 * Navigation buttons for Pagenation (prev/next) 266 * 267 * @param string $id page id or media id 268 * @param int $first 269 * @param bool $hasNext 270 * @return array html 271 */ 272 protected function htmlNavigation($id, $first, $hasNext) 273 { 274 global $conf; 275 276 $html = '<div class="pagenav">'; 277 $last = $first + $conf['recent']; 278 if ($first > 0) { 279 $first = max($first - $conf['recent'], 0); 280 $html.= '<div class="pagenav-prev">'; 281 if ($this->media_id) { 282 $html.= html_btn('newer', $id, "p", media_managerURL(['first' => $first], '&', false, true)); 283 } else { 284 $html.= html_btn('newer', $id, "p" ,['do' => 'revisions', 'first' => $first]); 285 } 286 $html.= '</div>'; 287 } 288 if ($hasNext) { 289 $html.= '<div class="pagenav-next">'; 290 if ($this->media_id) { 291 $html.= html_btn('older', $id, "n", media_managerURL(['first' => $last], '&', false, true)); 292 } else { 293 $html.= html_btn('older', $id, "n", ['do' => 'revisions', 'first' => $last]); 294 } 295 $html.= '</div>'; 296 } 297 $html.= '</div>'; 298 return $html; 299 } 300 301 /** 302 * Returns instance of objRevInfo 303 * 304 * @param array $info Revision info structure of a page or media file 305 * @return objRevInfo object (anonymous class) 306 */ 307 protected function getObjRevInfo(array $info) 308 { 309 return new class ($info) // anonymous class (objRevInfo) 310 { 311 protected $info; 312 313 public function __construct(array $info) 314 { 315 $this->info = $info; 316 } 317 318 // current indicator 319 public function currentIndicator() 320 { 321 global $lang; 322 return ($this->info['current']) ? '('.$lang['current'].')' : ''; 323 } 324 325 // edit date and time of the page or media file 326 public function editDate() 327 { 328 return '<span class="date">'. dformat($this->info['date']) .'</span>'; 329 } 330 331 // edit summary 332 public function editSummary() 333 { 334 return '<span class="sum">'.' – '. hsc($this->info['sum']).'</span>'; 335 } 336 337 // editor of the page or media file 338 public function editor() 339 { 340 // slightly different with display of Ui\Recent, i.e. external edit 341 global $lang; 342 $html = '<span class="user">'; 343 if (!$this->info['user'] && !$this->info['ip']) { 344 $html.= '('.$lang['external_edit'].')'; 345 } elseif ($this->info['user']) { 346 $html.= '<bdi>'. editorinfo($this->info['user']) .'</bdi>'; 347 if (auth_ismanager()) $html.= ' <bdo dir="ltr">('. $this->info['ip'] .')</bdo>'; 348 } else { 349 $html.= '<bdo dir="ltr">'. $this->info['ip'] .'</bdo>'; 350 } 351 $html.= '</span>'; 352 return $html; 353 } 354 355 // name of the page or media file 356 public function itemName() 357 { 358 // slightly different with display of Ui\Recent, i.e. revison may not exists 359 $id = $this->info['id']; 360 $rev = $this->info['date']; 361 362 if (isset($this->info['media'])) { 363 // media file revision 364 if (isset($this->info['current'])) { 365 $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view'], '&'); 366 $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>'; 367 } elseif (file_exists(mediaFN($id, $rev))) { 368 $href = media_managerURL(['image'=> $id, 'tab_details'=> 'view', 'rev'=> $rev], '&'); 369 $html = '<a href="'.$href.'" class="wikilink1">'.$id.'</a>'; 370 } else { 371 $html = $id; 372 } 373 return $html; 374 } else { 375 // page revision 376 $display_name = useHeading('navigation') ? hsc(p_get_first_heading($id)) : $id; 377 if (!$display_name) $display_name = $id; 378 if ($this->info['current'] || page_exists($id, $rev)) { 379 $href = wl($id, "rev=$rev", false, '&'); 380 $html = '<a href="'.$href.'" class="wikilink1">'.$display_name.'</a>'; 381 } else { 382 $html = $display_name; 383 } 384 return $html; 385 } 386 } 387 388 // icon difflink 389 public function difflink() 390 { 391 global $lang; 392 $id = $this->info['id']; 393 $rev = $this->info['date']; 394 395 if (isset($this->info['media'])) { 396 // media file revision 397 if (isset($this->info['current']) || !file_exists(mediaFN($id, $rev))) { 398 $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'; 399 } else { 400 $href = media_managerURL(['image'=> $id, 'rev'=> $rev, 'mediado'=>'diff'], '&'); 401 $html = '<a href="'.$href.'" class="diff_link">' 402 . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"' 403 . ' title="'. $lang['diff'] .'" alt="'.$lang['diff'] .'" />' 404 . '</a> '; 405 } 406 return $html; 407 } else { 408 // page revision 409 if ($this->info['current'] || !page_exists($id, $rev)) { 410 $html = '<img src="'.DOKU_BASE.'lib/images/blank.gif" width="15" height="11" alt="" />'; 411 } else { 412 $href = wl($id, "rev=$rev,do=diff", false, '&'); 413 $html = '<a href="'.$href.'" class="diff_link">' 414 . '<img src="'.DOKU_BASE.'lib/images/diff.png" width="15" height="11"' 415 . ' title="'.$lang['diff'].'" alt="'.$lang['diff'].'" />' 416 . '</a>'; 417 } 418 return $html; 419 } 420 } 421 422 // size change 423 public function sizeChange() 424 { 425 $class = 'sizechange'; 426 $value = filesize_h(abs($this->info['sizechange'])); 427 if ($this->info['sizechange'] > 0) { 428 $class .= ' positive'; 429 $value = '+' . $value; 430 } elseif ($this->info['sizechange'] < 0) { 431 $class .= ' negative'; 432 $value = '-' . $value; 433 } else { 434 $value = '±' . $value; 435 } 436 return '<span class="'.$class.'">'.$value.'</span>'; 437 } 438 }; // end of anonymous class (objRevInfo) 439 } 440 441} 442