[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Page history 4 * 5 * Split off from Article.php and Skin.php, 2003-12-22 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License along 18 * with this program; if not, write to the Free Software Foundation, Inc., 19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * http://www.gnu.org/copyleft/gpl.html 21 * 22 * @file 23 * @ingroup Actions 24 */ 25 26 /** 27 * This class handles printing the history page for an article. In order to 28 * be efficient, it uses timestamps rather than offsets for paging, to avoid 29 * costly LIMIT,offset queries. 30 * 31 * Construct it by passing in an Article, and call $h->history() to print the 32 * history. 33 * 34 * @ingroup Actions 35 */ 36 class HistoryAction extends FormlessAction { 37 const DIR_PREV = 0; 38 const DIR_NEXT = 1; 39 40 /** @var array Array of message keys and strings */ 41 public $message; 42 43 public function getName() { 44 return 'history'; 45 } 46 47 public function requiresWrite() { 48 return false; 49 } 50 51 public function requiresUnblock() { 52 return false; 53 } 54 55 protected function getPageTitle() { 56 return $this->msg( 'history-title', $this->getTitle()->getPrefixedText() )->text(); 57 } 58 59 protected function getDescription() { 60 // Creation of a subtitle link pointing to [[Special:Log]] 61 return Linker::linkKnown( 62 SpecialPage::getTitleFor( 'Log' ), 63 $this->msg( 'viewpagelogs' )->escaped(), 64 array(), 65 array( 'page' => $this->getTitle()->getPrefixedText() ) 66 ); 67 } 68 69 /** 70 * Get the Article object we are working on. 71 * @return Page 72 */ 73 public function getArticle() { 74 return $this->page; 75 } 76 77 /** 78 * As we use the same small set of messages in various methods and that 79 * they are called often, we call them once and save them in $this->message 80 */ 81 private function preCacheMessages() { 82 // Precache various messages 83 if ( !isset( $this->message ) ) { 84 $msgs = array( 'cur', 'last', 'pipe-separator' ); 85 foreach ( $msgs as $msg ) { 86 $this->message[$msg] = $this->msg( $msg )->escaped(); 87 } 88 } 89 } 90 91 /** 92 * Print the history page for an article. 93 */ 94 function onView() { 95 $out = $this->getOutput(); 96 $request = $this->getRequest(); 97 98 /** 99 * Allow client caching. 100 */ 101 if ( $out->checkLastModified( $this->page->getTouched() ) ) { 102 return; // Client cache fresh and headers sent, nothing more to do. 103 } 104 105 wfProfileIn( __METHOD__ ); 106 107 $this->preCacheMessages(); 108 $config = $this->context->getConfig(); 109 110 # Fill in the file cache if not set already 111 $useFileCache = $config->get( 'UseFileCache' ); 112 if ( $useFileCache && HTMLFileCache::useFileCache( $this->getContext() ) ) { 113 $cache = new HTMLFileCache( $this->getTitle(), 'history' ); 114 if ( !$cache->isCacheGood( /* Assume up to date */ ) ) { 115 ob_start( array( &$cache, 'saveToFileCache' ) ); 116 } 117 } 118 119 // Setup page variables. 120 $out->setFeedAppendQuery( 'action=history' ); 121 $out->addModules( 'mediawiki.action.history' ); 122 if ( $config->get( 'UseMediaWikiUIEverywhere' ) ) { 123 $out = $this->getOutput(); 124 $out->addModuleStyles( array( 125 'mediawiki.ui.input', 126 'mediawiki.ui.checkbox', 127 ) ); 128 } 129 130 // Handle atom/RSS feeds. 131 $feedType = $request->getVal( 'feed' ); 132 if ( $feedType ) { 133 $this->feed( $feedType ); 134 wfProfileOut( __METHOD__ ); 135 136 return; 137 } 138 139 // Fail nicely if article doesn't exist. 140 if ( !$this->page->exists() ) { 141 $out->addWikiMsg( 'nohistory' ); 142 # show deletion/move log if there is an entry 143 LogEventsList::showLogExtract( 144 $out, 145 array( 'delete', 'move' ), 146 $this->getTitle(), 147 '', 148 array( 'lim' => 10, 149 'conds' => array( "log_action != 'revision'" ), 150 'showIfEmpty' => false, 151 'msgKey' => array( 'moveddeleted-notice' ) 152 ) 153 ); 154 wfProfileOut( __METHOD__ ); 155 156 return; 157 } 158 159 /** 160 * Add date selector to quickly get to a certain time 161 */ 162 $year = $request->getInt( 'year' ); 163 $month = $request->getInt( 'month' ); 164 $tagFilter = $request->getVal( 'tagfilter' ); 165 $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter ); 166 167 /** 168 * Option to show only revisions that have been (partially) hidden via RevisionDelete 169 */ 170 if ( $request->getBool( 'deleted' ) ) { 171 $conds = array( 'rev_deleted != 0' ); 172 } else { 173 $conds = array(); 174 } 175 if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) { 176 $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(), 177 'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n"; 178 } else { 179 $checkDeleted = ''; 180 } 181 182 // Add the general form 183 $action = htmlspecialchars( wfScript() ); 184 $out->addHTML( 185 "<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" . 186 Xml::fieldset( 187 $this->msg( 'history-fieldset-title' )->text(), 188 false, 189 array( 'id' => 'mw-history-search' ) 190 ) . 191 Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n" . 192 Html::hidden( 'action', 'history' ) . "\n" . 193 Xml::dateMenu( 194 ( $year == null ? MWTimestamp::getLocalInstance()->format( 'Y' ) : $year ), 195 $month 196 ) . ' ' . 197 ( $tagSelector ? ( implode( ' ', $tagSelector ) . ' ' ) : '' ) . 198 $checkDeleted . 199 Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" . 200 '</fieldset></form>' 201 ); 202 203 wfRunHooks( 'PageHistoryBeforeList', array( &$this->page, $this->getContext() ) ); 204 205 // Create and output the list. 206 $pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds ); 207 $out->addHTML( 208 $pager->getNavigationBar() . 209 $pager->getBody() . 210 $pager->getNavigationBar() 211 ); 212 $out->preventClickjacking( $pager->getPreventClickjacking() ); 213 214 wfProfileOut( __METHOD__ ); 215 } 216 217 /** 218 * Fetch an array of revisions, specified by a given limit, offset and 219 * direction. This is now only used by the feeds. It was previously 220 * used by the main UI but that's now handled by the pager. 221 * 222 * @param int $limit The limit number of revisions to get 223 * @param int $offset 224 * @param int $direction Either self::DIR_PREV or self::DIR_NEXT 225 * @return ResultWrapper 226 */ 227 function fetchRevisions( $limit, $offset, $direction ) { 228 // Fail if article doesn't exist. 229 if ( !$this->getTitle()->exists() ) { 230 return new FakeResultWrapper( array() ); 231 } 232 233 $dbr = wfGetDB( DB_SLAVE ); 234 235 if ( $direction === self::DIR_PREV ) { 236 list( $dirs, $oper ) = array( "ASC", ">=" ); 237 } else { /* $direction === self::DIR_NEXT */ 238 list( $dirs, $oper ) = array( "DESC", "<=" ); 239 } 240 241 if ( $offset ) { 242 $offsets = array( "rev_timestamp $oper " . $dbr->addQuotes( $dbr->timestamp( $offset ) ) ); 243 } else { 244 $offsets = array(); 245 } 246 247 $page_id = $this->page->getId(); 248 249 return $dbr->select( 'revision', 250 Revision::selectFields(), 251 array_merge( array( 'rev_page' => $page_id ), $offsets ), 252 __METHOD__, 253 array( 'ORDER BY' => "rev_timestamp $dirs", 254 'USE INDEX' => 'page_timestamp', 'LIMIT' => $limit ) 255 ); 256 } 257 258 /** 259 * Output a subscription feed listing recent edits to this page. 260 * 261 * @param string $type Feed type 262 */ 263 function feed( $type ) { 264 if ( !FeedUtils::checkFeedOutput( $type ) ) { 265 return; 266 } 267 $request = $this->getRequest(); 268 269 $feedClasses = $this->context->getConfig()->get( 'FeedClasses' ); 270 /** @var RSSFeed|AtomFeed $feed */ 271 $feed = new $feedClasses[$type]( 272 $this->getTitle()->getPrefixedText() . ' - ' . 273 $this->msg( 'history-feed-title' )->inContentLanguage()->text(), 274 $this->msg( 'history-feed-description' )->inContentLanguage()->text(), 275 $this->getTitle()->getFullURL( 'action=history' ) 276 ); 277 278 // Get a limit on number of feed entries. Provide a sane default 279 // of 10 if none is defined (but limit to $wgFeedLimit max) 280 $limit = $request->getInt( 'limit', 10 ); 281 $limit = min( 282 max( $limit, 1 ), 283 $this->context->getConfig()->get( 'FeedLimit' ) 284 ); 285 286 $items = $this->fetchRevisions( $limit, 0, self::DIR_NEXT ); 287 288 // Generate feed elements enclosed between header and footer. 289 $feed->outHeader(); 290 if ( $items->numRows() ) { 291 foreach ( $items as $row ) { 292 $feed->outItem( $this->feedItem( $row ) ); 293 } 294 } else { 295 $feed->outItem( $this->feedEmpty() ); 296 } 297 $feed->outFooter(); 298 } 299 300 function feedEmpty() { 301 return new FeedItem( 302 $this->msg( 'nohistory' )->inContentLanguage()->text(), 303 $this->msg( 'history-feed-empty' )->inContentLanguage()->parseAsBlock(), 304 $this->getTitle()->getFullURL(), 305 wfTimestamp( TS_MW ), 306 '', 307 $this->getTitle()->getTalkPage()->getFullURL() 308 ); 309 } 310 311 /** 312 * Generate a FeedItem object from a given revision table row 313 * Borrows Recent Changes' feed generation functions for formatting; 314 * includes a diff to the previous revision (if any). 315 * 316 * @param stdClass|array $row Database row 317 * @return FeedItem 318 */ 319 function feedItem( $row ) { 320 $rev = new Revision( $row ); 321 $rev->setTitle( $this->getTitle() ); 322 $text = FeedUtils::formatDiffRow( 323 $this->getTitle(), 324 $this->getTitle()->getPreviousRevisionID( $rev->getId() ), 325 $rev->getId(), 326 $rev->getTimestamp(), 327 $rev->getComment() 328 ); 329 if ( $rev->getComment() == '' ) { 330 global $wgContLang; 331 $title = $this->msg( 'history-feed-item-nocomment', 332 $rev->getUserText(), 333 $wgContLang->timeanddate( $rev->getTimestamp() ), 334 $wgContLang->date( $rev->getTimestamp() ), 335 $wgContLang->time( $rev->getTimestamp() ) )->inContentLanguage()->text(); 336 } else { 337 $title = $rev->getUserText() . 338 $this->msg( 'colon-separator' )->inContentLanguage()->text() . 339 FeedItem::stripComment( $rev->getComment() ); 340 } 341 342 return new FeedItem( 343 $title, 344 $text, 345 $this->getTitle()->getFullURL( 'diff=' . $rev->getId() . '&oldid=prev' ), 346 $rev->getTimestamp(), 347 $rev->getUserText(), 348 $this->getTitle()->getTalkPage()->getFullURL() 349 ); 350 } 351 } 352 353 /** 354 * @ingroup Pager 355 * @ingroup Actions 356 */ 357 class HistoryPager extends ReverseChronologicalPager { 358 /** 359 * @var bool|stdClass 360 */ 361 public $lastRow = false; 362 363 public $counter, $historyPage, $buttons, $conds; 364 365 protected $oldIdChecked; 366 367 protected $preventClickjacking = false; 368 /** 369 * @var array 370 */ 371 protected $parentLens; 372 373 /** 374 * @param HistoryAction $historyPage 375 * @param string $year 376 * @param string $month 377 * @param string $tagFilter 378 * @param array $conds 379 */ 380 function __construct( $historyPage, $year = '', $month = '', $tagFilter = '', $conds = array() ) { 381 parent::__construct( $historyPage->getContext() ); 382 $this->historyPage = $historyPage; 383 $this->tagFilter = $tagFilter; 384 $this->getDateCond( $year, $month ); 385 $this->conds = $conds; 386 } 387 388 // For hook compatibility... 389 function getArticle() { 390 return $this->historyPage->getArticle(); 391 } 392 393 function getSqlComment() { 394 if ( $this->conds ) { 395 return 'history page filtered'; // potentially slow, see CR r58153 396 } else { 397 return 'history page unfiltered'; 398 } 399 } 400 401 function getQueryInfo() { 402 $queryInfo = array( 403 'tables' => array( 'revision', 'user' ), 404 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ), 405 'conds' => array_merge( 406 array( 'rev_page' => $this->getWikiPage()->getId() ), 407 $this->conds ), 408 'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ), 409 'join_conds' => array( 'user' => Revision::userJoinCond() ), 410 ); 411 ChangeTags::modifyDisplayQuery( 412 $queryInfo['tables'], 413 $queryInfo['fields'], 414 $queryInfo['conds'], 415 $queryInfo['join_conds'], 416 $queryInfo['options'], 417 $this->tagFilter 418 ); 419 wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) ); 420 421 return $queryInfo; 422 } 423 424 function getIndexField() { 425 return 'rev_timestamp'; 426 } 427 428 /** 429 * @param stdClass $row 430 * @return string 431 */ 432 function formatRow( $row ) { 433 if ( $this->lastRow ) { 434 $latest = ( $this->counter == 1 && $this->mIsFirst ); 435 $firstInList = $this->counter == 1; 436 $this->counter++; 437 $s = $this->historyLine( $this->lastRow, $row, 438 $this->getTitle()->getNotificationTimestamp( $this->getUser() ), $latest, $firstInList ); 439 } else { 440 $s = ''; 441 } 442 $this->lastRow = $row; 443 444 return $s; 445 } 446 447 function doBatchLookups() { 448 # Do a link batch query 449 $this->mResult->seek( 0 ); 450 $batch = new LinkBatch(); 451 $revIds = array(); 452 foreach ( $this->mResult as $row ) { 453 if ( $row->rev_parent_id ) { 454 $revIds[] = $row->rev_parent_id; 455 } 456 if ( !is_null( $row->user_name ) ) { 457 $batch->add( NS_USER, $row->user_name ); 458 $batch->add( NS_USER_TALK, $row->user_name ); 459 } else { # for anons or usernames of imported revisions 460 $batch->add( NS_USER, $row->rev_user_text ); 461 $batch->add( NS_USER_TALK, $row->rev_user_text ); 462 } 463 } 464 $this->parentLens = Revision::getParentLengths( $this->mDb, $revIds ); 465 $batch->execute(); 466 $this->mResult->seek( 0 ); 467 } 468 469 /** 470 * Creates begin of history list with a submit button 471 * 472 * @return string HTML output 473 */ 474 function getStartBody() { 475 $this->lastRow = false; 476 $this->counter = 1; 477 $this->oldIdChecked = 0; 478 479 $this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' ); 480 $s = Html::openElement( 'form', array( 'action' => wfScript(), 481 'id' => 'mw-history-compare' ) ) . "\n"; 482 $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . "\n"; 483 $s .= Html::hidden( 'action', 'historysubmit' ) . "\n"; 484 485 // Button container stored in $this->buttons for re-use in getEndBody() 486 $this->buttons = '<div>'; 487 $className = 'historysubmit mw-history-compareselectedversions-button'; 488 if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { 489 $className .= ' mw-ui-button mw-ui-constructive'; 490 } 491 $this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(), 492 array( 'class' => $className ) 493 + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' ) 494 ) . "\n"; 495 496 if ( $this->getUser()->isAllowed( 'deleterevision' ) ) { 497 $this->buttons .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' ); 498 } 499 $this->buttons .= '</div>'; 500 501 $s .= $this->buttons; 502 $s .= '<ul id="pagehistory">' . "\n"; 503 504 return $s; 505 } 506 507 private function getRevisionButton( $name, $msg ) { 508 $this->preventClickjacking(); 509 # Note bug #20966, <button> is non-standard in IE<8 510 $element = Html::element( 511 'button', 512 array( 513 'type' => 'submit', 514 'name' => $name, 515 'value' => '1', 516 'class' => "historysubmit mw-history-$name-button", 517 ), 518 $this->msg( $msg )->text() 519 ) . "\n"; 520 return $element; 521 } 522 523 function getEndBody() { 524 if ( $this->lastRow ) { 525 $latest = $this->counter == 1 && $this->mIsFirst; 526 $firstInList = $this->counter == 1; 527 if ( $this->mIsBackwards ) { 528 # Next row is unknown, but for UI reasons, probably exists if an offset has been specified 529 if ( $this->mOffset == '' ) { 530 $next = null; 531 } else { 532 $next = 'unknown'; 533 } 534 } else { 535 # The next row is the past-the-end row 536 $next = $this->mPastTheEndRow; 537 } 538 $this->counter++; 539 $s = $this->historyLine( $this->lastRow, $next, 540 $this->getTitle()->getNotificationTimestamp( $this->getUser() ), $latest, $firstInList ); 541 } else { 542 $s = ''; 543 } 544 $s .= "</ul>\n"; 545 # Add second buttons only if there is more than one rev 546 if ( $this->getNumRows() > 2 ) { 547 $s .= $this->buttons; 548 } 549 $s .= '</form>'; 550 551 return $s; 552 } 553 554 /** 555 * Creates a submit button 556 * 557 * @param string $message Text of the submit button, will be escaped 558 * @param array $attributes Attributes 559 * @return string HTML output for the submit button 560 */ 561 function submitButton( $message, $attributes = array() ) { 562 # Disable submit button if history has 1 revision only 563 if ( $this->getNumRows() > 1 ) { 564 return Xml::submitButton( $message, $attributes ); 565 } else { 566 return ''; 567 } 568 } 569 570 /** 571 * Returns a row from the history printout. 572 * 573 * @todo document some more, and maybe clean up the code (some params redundant?) 574 * 575 * @param stdClass $row The database row corresponding to the previous line. 576 * @param mixed $next The database row corresponding to the next line 577 * (chronologically previous) 578 * @param bool|string $notificationtimestamp 579 * @param bool $latest Whether this row corresponds to the page's latest revision. 580 * @param bool $firstInList Whether this row corresponds to the first 581 * displayed on this history page. 582 * @return string HTML output for the row 583 */ 584 function historyLine( $row, $next, $notificationtimestamp = false, 585 $latest = false, $firstInList = false ) { 586 $rev = new Revision( $row ); 587 $rev->setTitle( $this->getTitle() ); 588 589 if ( is_object( $next ) ) { 590 $prevRev = new Revision( $next ); 591 $prevRev->setTitle( $this->getTitle() ); 592 } else { 593 $prevRev = null; 594 } 595 596 $curlink = $this->curLink( $rev, $latest ); 597 $lastlink = $this->lastLink( $rev, $next ); 598 $curLastlinks = $curlink . $this->historyPage->message['pipe-separator'] . $lastlink; 599 $histLinks = Html::rawElement( 600 'span', 601 array( 'class' => 'mw-history-histlinks' ), 602 $this->msg( 'parentheses' )->rawParams( $curLastlinks )->escaped() 603 ); 604 605 $diffButtons = $this->diffButtons( $rev, $firstInList ); 606 $s = $histLinks . $diffButtons; 607 608 $link = $this->revLink( $rev ); 609 $classes = array(); 610 611 $del = ''; 612 $user = $this->getUser(); 613 // Show checkboxes for each revision 614 if ( $user->isAllowed( 'deleterevision' ) ) { 615 $this->preventClickjacking(); 616 // If revision was hidden from sysops, disable the checkbox 617 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) { 618 $del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) ); 619 // Otherwise, enable the checkbox... 620 } else { 621 $del = Xml::check( 'showhiderevisions', false, 622 array( 'name' => 'ids[' . $rev->getId() . ']' ) ); 623 } 624 // User can only view deleted revisions... 625 } elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) { 626 // If revision was hidden from sysops, disable the link 627 if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) { 628 $del = Linker::revDeleteLinkDisabled( false ); 629 // Otherwise, show the link... 630 } else { 631 $query = array( 'type' => 'revision', 632 'target' => $this->getTitle()->getPrefixedDBkey(), 'ids' => $rev->getId() ); 633 $del .= Linker::revDeleteLink( $query, 634 $rev->isDeleted( Revision::DELETED_RESTRICTED ), false ); 635 } 636 } 637 if ( $del ) { 638 $s .= " $del "; 639 } 640 641 $lang = $this->getLanguage(); 642 $dirmark = $lang->getDirMark(); 643 644 $s .= " $link"; 645 $s .= $dirmark; 646 $s .= " <span class='history-user'>" . 647 Linker::revUserTools( $rev, true ) . "</span>"; 648 $s .= $dirmark; 649 650 if ( $rev->isMinor() ) { 651 $s .= ' ' . ChangesList::flag( 'minor' ); 652 } 653 654 # Sometimes rev_len isn't populated 655 if ( $rev->getSize() !== null ) { 656 # Size is always public data 657 $prevSize = isset( $this->parentLens[$row->rev_parent_id] ) 658 ? $this->parentLens[$row->rev_parent_id] 659 : 0; 660 $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() ); 661 $fSize = Linker::formatRevisionSize( $rev->getSize() ); 662 $s .= ' <span class="mw-changeslist-separator">. .</span> ' . "$fSize $sDiff"; 663 } 664 665 # Text following the character difference is added just before running hooks 666 $s2 = Linker::revComment( $rev, false, true ); 667 668 if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) { 669 $s2 .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>'; 670 $classes[] = 'mw-history-line-updated'; 671 } 672 673 $tools = array(); 674 675 # Rollback and undo links 676 if ( $prevRev && $this->getTitle()->quickUserCan( 'edit', $user ) ) { 677 if ( $latest && $this->getTitle()->quickUserCan( 'rollback', $user ) ) { 678 // Get a rollback link without the brackets 679 $rollbackLink = Linker::generateRollback( 680 $rev, 681 $this->getContext(), 682 array( 'verify', 'noBrackets' ) 683 ); 684 if ( $rollbackLink ) { 685 $this->preventClickjacking(); 686 $tools[] = $rollbackLink; 687 } 688 } 689 690 if ( !$rev->isDeleted( Revision::DELETED_TEXT ) 691 && !$prevRev->isDeleted( Revision::DELETED_TEXT ) 692 ) { 693 # Create undo tooltip for the first (=latest) line only 694 $undoTooltip = $latest 695 ? array( 'title' => $this->msg( 'tooltip-undo' )->text() ) 696 : array(); 697 $undolink = Linker::linkKnown( 698 $this->getTitle(), 699 $this->msg( 'editundo' )->escaped(), 700 $undoTooltip, 701 array( 702 'action' => 'edit', 703 'undoafter' => $prevRev->getId(), 704 'undo' => $rev->getId() 705 ) 706 ); 707 $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>"; 708 } 709 } 710 // Allow extension to add their own links here 711 wfRunHooks( 'HistoryRevisionTools', array( $rev, &$tools ) ); 712 713 if ( $tools ) { 714 $s2 .= ' ' . $this->msg( 'parentheses' )->rawParams( $lang->pipeList( $tools ) )->escaped(); 715 } 716 717 # Tags 718 list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' ); 719 $classes = array_merge( $classes, $newClasses ); 720 if ( $tagSummary !== '' ) { 721 $s2 .= " $tagSummary"; 722 } 723 724 # Include separator between character difference and following text 725 if ( $s2 !== '' ) { 726 $s .= ' <span class="mw-changeslist-separator">. .</span> ' . $s2; 727 } 728 729 wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row, &$s, &$classes ) ); 730 731 $attribs = array(); 732 if ( $classes ) { 733 $attribs['class'] = implode( ' ', $classes ); 734 } 735 736 return Xml::tags( 'li', $attribs, $s ) . "\n"; 737 } 738 739 /** 740 * Create a link to view this revision of the page 741 * 742 * @param Revision $rev 743 * @return string 744 */ 745 function revLink( $rev ) { 746 $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $this->getUser() ); 747 $date = htmlspecialchars( $date ); 748 if ( $rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { 749 $link = Linker::linkKnown( 750 $this->getTitle(), 751 $date, 752 array( 'class' => 'mw-changeslist-date' ), 753 array( 'oldid' => $rev->getId() ) 754 ); 755 } else { 756 $link = $date; 757 } 758 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { 759 $link = "<span class=\"history-deleted\">$link</span>"; 760 } 761 762 return $link; 763 } 764 765 /** 766 * Create a diff-to-current link for this revision for this page 767 * 768 * @param Revision $rev 769 * @param bool $latest This is the latest revision of the page? 770 * @return string 771 */ 772 function curLink( $rev, $latest ) { 773 $cur = $this->historyPage->message['cur']; 774 if ( $latest || !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { 775 return $cur; 776 } else { 777 return Linker::linkKnown( 778 $this->getTitle(), 779 $cur, 780 array(), 781 array( 782 'diff' => $this->getWikiPage()->getLatest(), 783 'oldid' => $rev->getId() 784 ) 785 ); 786 } 787 } 788 789 /** 790 * Create a diff-to-previous link for this revision for this page. 791 * 792 * @param Revision $prevRev The revision being displayed 793 * @param stdClass|string|null $next The next revision in list (that is 794 * the previous one in chronological order). 795 * May either be a row, "unknown" or null. 796 * @return string 797 */ 798 function lastLink( $prevRev, $next ) { 799 $last = $this->historyPage->message['last']; 800 801 if ( $next === null ) { 802 # Probably no next row 803 return $last; 804 } 805 806 if ( $next === 'unknown' ) { 807 # Next row probably exists but is unknown, use an oldid=prev link 808 return Linker::linkKnown( 809 $this->getTitle(), 810 $last, 811 array(), 812 array( 813 'diff' => $prevRev->getId(), 814 'oldid' => 'prev' 815 ) 816 ); 817 } 818 819 $nextRev = new Revision( $next ); 820 821 if ( !$prevRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) 822 || !$nextRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) 823 ) { 824 return $last; 825 } 826 827 return Linker::linkKnown( 828 $this->getTitle(), 829 $last, 830 array(), 831 array( 832 'diff' => $prevRev->getId(), 833 'oldid' => $next->rev_id 834 ) 835 ); 836 } 837 838 /** 839 * Create radio buttons for page history 840 * 841 * @param Revision $rev 842 * @param bool $firstInList Is this version the first one? 843 * 844 * @return string HTML output for the radio buttons 845 */ 846 function diffButtons( $rev, $firstInList ) { 847 if ( $this->getNumRows() > 1 ) { 848 $id = $rev->getId(); 849 $radio = array( 'type' => 'radio', 'value' => $id ); 850 /** @todo Move title texts to javascript */ 851 if ( $firstInList ) { 852 $first = Xml::element( 'input', 853 array_merge( $radio, array( 854 'style' => 'visibility:hidden', 855 'name' => 'oldid', 856 'id' => 'mw-oldid-null' ) ) 857 ); 858 $checkmark = array( 'checked' => 'checked' ); 859 } else { 860 # Check visibility of old revisions 861 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { 862 $radio['disabled'] = 'disabled'; 863 $checkmark = array(); // We will check the next possible one 864 } elseif ( !$this->oldIdChecked ) { 865 $checkmark = array( 'checked' => 'checked' ); 866 $this->oldIdChecked = $id; 867 } else { 868 $checkmark = array(); 869 } 870 $first = Xml::element( 'input', 871 array_merge( $radio, $checkmark, array( 872 'name' => 'oldid', 873 'id' => "mw-oldid-$id" ) ) ); 874 $checkmark = array(); 875 } 876 $second = Xml::element( 'input', 877 array_merge( $radio, $checkmark, array( 878 'name' => 'diff', 879 'id' => "mw-diff-$id" ) ) ); 880 881 return $first . $second; 882 } else { 883 return ''; 884 } 885 } 886 887 /** 888 * This is called if a write operation is possible from the generated HTML 889 * @param bool $enable 890 */ 891 function preventClickjacking( $enable = true ) { 892 $this->preventClickjacking = $enable; 893 } 894 895 /** 896 * Get the "prevent clickjacking" flag 897 * @return bool 898 */ 899 function getPreventClickjacking() { 900 return $this->preventClickjacking; 901 } 902 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |