[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Base class for all changes lists. 4 * 5 * The class is used for formatting recent changes, related changes and watchlist. 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 */ 24 25 class ChangesList extends ContextSource { 26 /** 27 * @var Skin 28 */ 29 public $skin; 30 31 protected $watchlist = false; 32 protected $lastdate; 33 protected $message; 34 protected $rc_cache; 35 protected $rcCacheIndex; 36 protected $rclistOpen; 37 protected $rcMoveIndex; 38 39 /** @var MapCacheLRU */ 40 protected $watchingCache; 41 42 /** 43 * Changeslist constructor 44 * 45 * @param Skin|IContextSource $obj 46 */ 47 public function __construct( $obj ) { 48 if ( $obj instanceof IContextSource ) { 49 $this->setContext( $obj ); 50 $this->skin = $obj->getSkin(); 51 } else { 52 $this->setContext( $obj->getContext() ); 53 $this->skin = $obj; 54 } 55 $this->preCacheMessages(); 56 $this->watchingCache = new MapCacheLRU( 50 ); 57 } 58 59 /** 60 * Fetch an appropriate changes list class for the specified context 61 * Some users might want to use an enhanced list format, for instance 62 * 63 * @param IContextSource $context 64 * @return ChangesList 65 */ 66 public static function newFromContext( IContextSource $context ) { 67 $user = $context->getUser(); 68 $sk = $context->getSkin(); 69 $list = null; 70 if ( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) { 71 $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) ); 72 73 return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context ); 74 } else { 75 return $list; 76 } 77 } 78 79 /** 80 * Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag 81 * @param bool $value 82 */ 83 public function setWatchlistDivs( $value = true ) { 84 $this->watchlist = $value; 85 } 86 87 /** 88 * @return bool True when setWatchlistDivs has been called 89 * @since 1.23 90 */ 91 public function isWatchlist() { 92 return (bool)$this->watchlist; 93 } 94 95 /** 96 * As we use the same small set of messages in various methods and that 97 * they are called often, we call them once and save them in $this->message 98 */ 99 private function preCacheMessages() { 100 if ( !isset( $this->message ) ) { 101 foreach ( array( 102 'cur', 'diff', 'hist', 'enhancedrc-history', 'last', 'blocklink', 'history', 103 'semicolon-separator', 'pipe-separator' ) as $msg 104 ) { 105 $this->message[$msg] = $this->msg( $msg )->escaped(); 106 } 107 } 108 } 109 110 /** 111 * Returns the appropriate flags for new page, minor change and patrolling 112 * @param array $flags Associative array of 'flag' => Bool 113 * @param string $nothing To use for empty space 114 * @return string 115 */ 116 public function recentChangesFlags( $flags, $nothing = ' ' ) { 117 $f = ''; 118 foreach ( array_keys( $this->getConfig()->get( 'RecentChangesFlags' ) ) as $flag ) { 119 $f .= isset( $flags[$flag] ) && $flags[$flag] 120 ? self::flag( $flag ) 121 : $nothing; 122 } 123 124 return $f; 125 } 126 127 /** 128 * Provide the "<abbr>" element appropriate to a given abbreviated flag, 129 * namely the flag indicating a new page, a minor edit, a bot edit, or an 130 * unpatrolled edit. By default in English it will contain "N", "m", "b", 131 * "!" respectively, plus it will have an appropriate title and class. 132 * 133 * @param string $flag One key of $wgRecentChangesFlags 134 * @return string Raw HTML 135 */ 136 public static function flag( $flag ) { 137 static $flagInfos = null; 138 if ( is_null( $flagInfos ) ) { 139 global $wgRecentChangesFlags; 140 $flagInfos = array(); 141 foreach ( $wgRecentChangesFlags as $key => $value ) { 142 $flagInfos[$key]['letter'] = wfMessage( $value['letter'] )->escaped(); 143 $flagInfos[$key]['title'] = wfMessage( $value['title'] )->escaped(); 144 // Allow customized class name, fall back to flag name 145 $flagInfos[$key]['class'] = Sanitizer::escapeClass( 146 isset( $value['class'] ) ? $value['class'] : $key ); 147 } 148 } 149 150 // Inconsistent naming, bleh, kepted for b/c 151 $map = array( 152 'minoredit' => 'minor', 153 'botedit' => 'bot', 154 ); 155 if ( isset( $map[$flag] ) ) { 156 $flag = $map[$flag]; 157 } 158 159 return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" . 160 $flagInfos[$flag]['title'] . "'>" . $flagInfos[$flag]['letter'] . 161 '</abbr>'; 162 } 163 164 /** 165 * Returns text for the start of the tabular part of RC 166 * @return string 167 */ 168 public function beginRecentChangesList() { 169 $this->rc_cache = array(); 170 $this->rcMoveIndex = 0; 171 $this->rcCacheIndex = 0; 172 $this->lastdate = ''; 173 $this->rclistOpen = false; 174 $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' ); 175 176 return '<div class="mw-changeslist">'; 177 } 178 179 /** 180 * @param ResultWrapper|array $rows 181 */ 182 public function initChangesListRows( $rows ) { 183 wfRunHooks( 'ChangesListInitRows', array( $this, $rows ) ); 184 } 185 186 /** 187 * Show formatted char difference 188 * @param int $old Number of bytes 189 * @param int $new Number of bytes 190 * @param IContextSource $context 191 * @return string 192 */ 193 public static function showCharacterDifference( $old, $new, IContextSource $context = null ) { 194 if ( !$context ) { 195 $context = RequestContext::getMain(); 196 } 197 198 $new = (int)$new; 199 $old = (int)$old; 200 $szdiff = $new - $old; 201 202 $lang = $context->getLanguage(); 203 $config = $context->getConfig(); 204 $code = $lang->getCode(); 205 static $fastCharDiff = array(); 206 if ( !isset( $fastCharDiff[$code] ) ) { 207 $fastCharDiff[$code] = $config->get( 'MiserMode' ) || $context->msg( 'rc-change-size' )->plain() === '$1'; 208 } 209 210 $formattedSize = $lang->formatNum( $szdiff ); 211 212 if ( !$fastCharDiff[$code] ) { 213 $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text(); 214 } 215 216 if ( abs( $szdiff ) > abs( $config->get( 'RCChangedSizeThreshold' ) ) ) { 217 $tag = 'strong'; 218 } else { 219 $tag = 'span'; 220 } 221 222 if ( $szdiff === 0 ) { 223 $formattedSizeClass = 'mw-plusminus-null'; 224 } elseif ( $szdiff > 0 ) { 225 $formattedSize = '+' . $formattedSize; 226 $formattedSizeClass = 'mw-plusminus-pos'; 227 } else { 228 $formattedSizeClass = 'mw-plusminus-neg'; 229 } 230 231 $formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text(); 232 233 return Html::element( $tag, 234 array( 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ), 235 $context->msg( 'parentheses', $formattedSize )->plain() ) . $lang->getDirMark(); 236 } 237 238 /** 239 * Format the character difference of one or several changes. 240 * 241 * @param RecentChange $old 242 * @param RecentChange $new Last change to use, if not provided, $old will be used 243 * @return string HTML fragment 244 */ 245 public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) { 246 $oldlen = $old->mAttribs['rc_old_len']; 247 248 if ( $new ) { 249 $newlen = $new->mAttribs['rc_new_len']; 250 } else { 251 $newlen = $old->mAttribs['rc_new_len']; 252 } 253 254 if ( $oldlen === null || $newlen === null ) { 255 return ''; 256 } 257 258 return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() ); 259 } 260 261 /** 262 * Returns text for the end of RC 263 * @return string 264 */ 265 public function endRecentChangesList() { 266 $out = $this->rclistOpen ? "</ul>\n" : ''; 267 $out .= '</div>'; 268 269 return $out; 270 } 271 272 /** 273 * @param string $s HTML to update 274 * @param mixed $rc_timestamp 275 */ 276 public function insertDateHeader( &$s, $rc_timestamp ) { 277 # Make date header if necessary 278 $date = $this->getLanguage()->userDate( $rc_timestamp, $this->getUser() ); 279 if ( $date != $this->lastdate ) { 280 if ( $this->lastdate != '' ) { 281 $s .= "</ul>\n"; 282 } 283 $s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">"; 284 $this->lastdate = $date; 285 $this->rclistOpen = true; 286 } 287 } 288 289 /** 290 * @param string $s HTML to update 291 * @param Title $title 292 * @param string $logtype 293 */ 294 public function insertLog( &$s, $title, $logtype ) { 295 $page = new LogPage( $logtype ); 296 $logname = $page->getName()->escaped(); 297 $s .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $title, $logname ) )->escaped(); 298 } 299 300 /** 301 * @param string $s HTML to update 302 * @param RecentChange $rc 303 * @param bool $unpatrolled 304 */ 305 public function insertDiffHist( &$s, &$rc, $unpatrolled ) { 306 # Diff link 307 if ( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) { 308 $diffLink = $this->message['diff']; 309 } elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) { 310 $diffLink = $this->message['diff']; 311 } else { 312 $query = array( 313 'curid' => $rc->mAttribs['rc_cur_id'], 314 'diff' => $rc->mAttribs['rc_this_oldid'], 315 'oldid' => $rc->mAttribs['rc_last_oldid'] 316 ); 317 318 $diffLink = Linker::linkKnown( 319 $rc->getTitle(), 320 $this->message['diff'], 321 array( 'tabindex' => $rc->counter ), 322 $query 323 ); 324 } 325 $diffhist = $diffLink . $this->message['pipe-separator']; 326 # History link 327 $diffhist .= Linker::linkKnown( 328 $rc->getTitle(), 329 $this->message['hist'], 330 array(), 331 array( 332 'curid' => $rc->mAttribs['rc_cur_id'], 333 'action' => 'history' 334 ) 335 ); 336 // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? 337 $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . 338 ' <span class="mw-changeslist-separator">. .</span> '; 339 } 340 341 /** 342 * @param string $s HTML to update 343 * @param RecentChange $rc 344 * @param bool $unpatrolled 345 * @param bool $watched 346 */ 347 public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) { 348 $params = array(); 349 if ( $rc->getTitle()->isRedirect() ) { 350 $params = array( 'redirect' => 'no' ); 351 } 352 353 $articlelink = Linker::linkKnown( 354 $rc->getTitle(), 355 null, 356 array( 'class' => 'mw-changeslist-title' ), 357 $params 358 ); 359 if ( $this->isDeleted( $rc, Revision::DELETED_TEXT ) ) { 360 $articlelink = '<span class="history-deleted">' . $articlelink . '</span>'; 361 } 362 # To allow for boldening pages watched by this user 363 $articlelink = "<span class=\"mw-title\">{$articlelink}</span>"; 364 # RTL/LTR marker 365 $articlelink .= $this->getLanguage()->getDirMark(); 366 367 wfRunHooks( 'ChangesListInsertArticleLink', 368 array( &$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched ) ); 369 370 $s .= " $articlelink"; 371 } 372 373 /** 374 * Get the timestamp from $rc formatted with current user's settings 375 * and a separator 376 * 377 * @param RecentChange $rc 378 * @return string HTML fragment 379 */ 380 public function getTimestamp( $rc ) { 381 // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? 382 return $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' . 383 $this->getLanguage()->userTime( 384 $rc->mAttribs['rc_timestamp'], 385 $this->getUser() 386 ) . '</span> <span class="mw-changeslist-separator">. .</span> '; 387 } 388 389 /** 390 * Insert time timestamp string from $rc into $s 391 * 392 * @param string $s HTML to update 393 * @param RecentChange $rc 394 */ 395 public function insertTimestamp( &$s, $rc ) { 396 $s .= $this->getTimestamp( $rc ); 397 } 398 399 /** 400 * Insert links to user page, user talk page and eventually a blocking link 401 * 402 * @param string &$s HTML to update 403 * @param RecentChange &$rc 404 */ 405 public function insertUserRelatedLinks( &$s, &$rc ) { 406 if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) { 407 $s .= ' <span class="history-deleted">' . 408 $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; 409 } else { 410 $s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'], 411 $rc->mAttribs['rc_user_text'] ); 412 $s .= Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); 413 } 414 } 415 416 /** 417 * Insert a formatted action 418 * 419 * @param RecentChange $rc 420 * @return string 421 */ 422 public function insertLogEntry( $rc ) { 423 $formatter = LogFormatter::newFromRow( $rc->mAttribs ); 424 $formatter->setContext( $this->getContext() ); 425 $formatter->setShowUserToolLinks( true ); 426 $mark = $this->getLanguage()->getDirMark(); 427 428 return $formatter->getActionText() . " $mark" . $formatter->getComment(); 429 } 430 431 /** 432 * Insert a formatted comment 433 * @param RecentChange $rc 434 * @return string 435 */ 436 public function insertComment( $rc ) { 437 if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) { 438 return ' <span class="history-deleted">' . 439 $this->msg( 'rev-deleted-comment' )->escaped() . '</span>'; 440 } else { 441 return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); 442 } 443 } 444 445 /** 446 * Check whether to enable recent changes patrol features 447 * 448 * @deprecated since 1.22 449 * @return bool 450 */ 451 public static function usePatrol() { 452 global $wgUser; 453 454 wfDeprecated( __METHOD__, '1.22' ); 455 456 return $wgUser->useRCPatrol(); 457 } 458 459 /** 460 * Returns the string which indicates the number of watching users 461 * @param int $count Number of user watching a page 462 * @return string 463 */ 464 protected function numberofWatchingusers( $count ) { 465 $cache = $this->watchingCache; 466 if ( $count > 0 ) { 467 if ( !$cache->has( $count ) ) { 468 $cache->set( $count, $this->msg( 'number_of_watching_users_RCview' ) 469 ->numParams( $count )->escaped() ); 470 } 471 472 return $cache->get( $count ); 473 } else { 474 return ''; 475 } 476 } 477 478 /** 479 * Determine if said field of a revision is hidden 480 * @param RCCacheEntry|RecentChange $rc 481 * @param int $field One of DELETED_* bitfield constants 482 * @return bool 483 */ 484 public static function isDeleted( $rc, $field ) { 485 return ( $rc->mAttribs['rc_deleted'] & $field ) == $field; 486 } 487 488 /** 489 * Determine if the current user is allowed to view a particular 490 * field of this revision, if it's marked as deleted. 491 * @param RCCacheEntry|RecentChange $rc 492 * @param int $field 493 * @param User $user User object to check, or null to use $wgUser 494 * @return bool 495 */ 496 public static function userCan( $rc, $field, User $user = null ) { 497 if ( $rc->mAttribs['rc_type'] == RC_LOG ) { 498 return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user ); 499 } else { 500 return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user ); 501 } 502 } 503 504 /** 505 * @param string $link 506 * @param bool $watched 507 * @return string 508 */ 509 protected function maybeWatchedLink( $link, $watched = false ) { 510 if ( $watched ) { 511 return '<strong class="mw-watched">' . $link . '</strong>'; 512 } else { 513 return '<span class="mw-rc-unwatched">' . $link . '</span>'; 514 } 515 } 516 517 /** Inserts a rollback link 518 * 519 * @param string $s 520 * @param RecentChange $rc 521 */ 522 public function insertRollback( &$s, &$rc ) { 523 if ( $rc->mAttribs['rc_type'] == RC_EDIT 524 && $rc->mAttribs['rc_this_oldid'] 525 && $rc->mAttribs['rc_cur_id'] 526 ) { 527 $page = $rc->getTitle(); 528 /** Check for rollback and edit permissions, disallow special pages, and only 529 * show a link on the top-most revision */ 530 if ( $this->getUser()->isAllowed( 'rollback' ) 531 && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] 532 ) { 533 $rev = new Revision( array( 534 'title' => $page, 535 'id' => $rc->mAttribs['rc_this_oldid'], 536 'user' => $rc->mAttribs['rc_user'], 537 'user_text' => $rc->mAttribs['rc_user_text'], 538 'deleted' => $rc->mAttribs['rc_deleted'] 539 ) ); 540 $s .= ' ' . Linker::generateRollback( $rev, $this->getContext() ); 541 } 542 } 543 } 544 545 /** 546 * @param string $s 547 * @param RecentChange $rc 548 * @param array $classes 549 */ 550 public function insertTags( &$s, &$rc, &$classes ) { 551 if ( empty( $rc->mAttribs['ts_tags'] ) ) { 552 return; 553 } 554 555 list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( 556 $rc->mAttribs['ts_tags'], 557 'changeslist' 558 ); 559 $classes = array_merge( $classes, $newClasses ); 560 $s .= ' ' . $tagSummary; 561 } 562 563 public function insertExtra( &$s, &$rc, &$classes ) { 564 // Empty, used for subclasses to add anything special. 565 } 566 567 protected function showAsUnpatrolled( RecentChange $rc ) { 568 return self::isUnpatrolled( $rc, $this->getUser() ); 569 } 570 571 /** 572 * @param object|RecentChange $rc Database row from recentchanges or a RecentChange object 573 * @param User $user 574 * @return bool 575 */ 576 public static function isUnpatrolled( $rc, User $user ) { 577 if ( $rc instanceof RecentChange ) { 578 $isPatrolled = $rc->mAttribs['rc_patrolled']; 579 $rcType = $rc->mAttribs['rc_type']; 580 } else { 581 $isPatrolled = $rc->rc_patrolled; 582 $rcType = $rc->rc_type; 583 } 584 585 if ( !$isPatrolled ) { 586 if ( $user->useRCPatrol() ) { 587 return true; 588 } 589 if ( $user->useNPPatrol() && $rcType == RC_NEW ) { 590 return true; 591 } 592 } 593 594 return false; 595 } 596 }
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 |