diff options
Diffstat (limited to 'includes/changes')
-rw-r--r-- | includes/changes/ChangesFeed.php | 240 | ||||
-rw-r--r-- | includes/changes/ChangesList.php | 250 | ||||
-rw-r--r-- | includes/changes/EnhancedChangesList.php | 336 | ||||
-rw-r--r-- | includes/changes/OldChangesList.php | 95 | ||||
-rw-r--r-- | includes/changes/RCCacheEntry.php | 16 | ||||
-rw-r--r-- | includes/changes/RCCacheEntryFactory.php | 275 | ||||
-rw-r--r-- | includes/changes/RecentChange.php | 523 |
7 files changed, 1128 insertions, 607 deletions
diff --git a/includes/changes/ChangesFeed.php b/includes/changes/ChangesFeed.php new file mode 100644 index 00000000..2d3b919d --- /dev/null +++ b/includes/changes/ChangesFeed.php @@ -0,0 +1,240 @@ +<?php +/** + * Feed for list of changes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Feed to Special:RecentChanges and Special:RecentChangesLiked + * + * @ingroup Feed + */ +class ChangesFeed { + public $format, $type, $titleMsg, $descMsg; + + /** + * Constructor + * + * @param string $format Feed's format (either 'rss' or 'atom') + * @param string $type Type of feed (for cache keys) + */ + public function __construct( $format, $type ) { + $this->format = $format; + $this->type = $type; + } + + /** + * Get a ChannelFeed subclass object to use + * + * @param string $title Feed's title + * @param string $description Feed's description + * @param string $url Url of origin page + * @return ChannelFeed|bool ChannelFeed subclass or false on failure + */ + public function getFeedObject( $title, $description, $url ) { + global $wgSitename, $wgLanguageCode, $wgFeedClasses; + + if ( !isset( $wgFeedClasses[$this->format] ) ) { + return false; + } + + if ( !array_key_exists( $this->format, $wgFeedClasses ) ) { + // falling back to atom + $this->format = 'atom'; + } + + $feedTitle = "$wgSitename - {$title} [$wgLanguageCode]"; + return new $wgFeedClasses[$this->format]( + $feedTitle, htmlspecialchars( $description ), $url ); + } + + /** + * Generates feed's content + * + * @param ChannelFeed $feed ChannelFeed subclass object (generally the one returned + * by getFeedObject()) + * @param ResultWrapper $rows ResultWrapper object with rows in recentchanges table + * @param int $lastmod Timestamp of the last item in the recentchanges table (only + * used for the cache key) + * @param FormOptions $opts As in SpecialRecentChanges::getDefaultOptions() + * @return null|bool True or null + */ + public function execute( $feed, $rows, $lastmod, $opts ) { + global $wgLang, $wgRenderHashAppend; + + if ( !FeedUtils::checkFeedOutput( $this->format ) ) { + return null; + } + + $optionsHash = md5( serialize( $opts->getAllValues() ) ) . $wgRenderHashAppend; + $timekey = wfMemcKey( $this->type, $this->format, $wgLang->getCode(), $optionsHash, 'timestamp' ); + $key = wfMemcKey( $this->type, $this->format, $wgLang->getCode(), $optionsHash ); + + FeedUtils::checkPurge( $timekey, $key ); + + /** + * Bumping around loading up diffs can be pretty slow, so where + * possible we want to cache the feed output so the next visitor + * gets it quick too. + */ + $cachedFeed = $this->loadFromCache( $lastmod, $timekey, $key ); + if ( is_string( $cachedFeed ) ) { + wfDebug( "RC: Outputting cached feed\n" ); + $feed->httpHeaders(); + echo $cachedFeed; + } else { + wfDebug( "RC: rendering new feed and caching it\n" ); + ob_start(); + self::generateFeed( $rows, $feed ); + $cachedFeed = ob_get_contents(); + ob_end_flush(); + $this->saveToCache( $cachedFeed, $timekey, $key ); + } + return true; + } + + /** + * Save to feed result to $messageMemc + * + * @param string $feed Feed's content + * @param string $timekey Memcached key of the last modification + * @param string $key Memcached key of the content + */ + public function saveToCache( $feed, $timekey, $key ) { + global $messageMemc; + $expire = 3600 * 24; # One day + $messageMemc->set( $key, $feed, $expire ); + $messageMemc->set( $timekey, wfTimestamp( TS_MW ), $expire ); + } + + /** + * Try to load the feed result from $messageMemc + * + * @param int $lastmod Timestamp of the last item in the recentchanges table + * @param string $timekey Memcached key of the last modification + * @param string $key Memcached key of the content + * @return string|bool Feed's content on cache hit or false on cache miss + */ + public function loadFromCache( $lastmod, $timekey, $key ) { + global $wgFeedCacheTimeout, $wgOut, $messageMemc; + + $feedLastmod = $messageMemc->get( $timekey ); + + if ( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) { + /** + * If the cached feed was rendered very recently, we may + * go ahead and use it even if there have been edits made + * since it was rendered. This keeps a swarm of requests + * from being too bad on a super-frequently edited wiki. + */ + + $feedAge = time() - wfTimestamp( TS_UNIX, $feedLastmod ); + $feedLastmodUnix = wfTimestamp( TS_UNIX, $feedLastmod ); + $lastmodUnix = wfTimestamp( TS_UNIX, $lastmod ); + + if ( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix ) { + wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" ); + if ( $feedLastmodUnix < $lastmodUnix ) { + $wgOut->setLastModified( $feedLastmod ); // bug 21916 + } + return $messageMemc->get( $key ); + } else { + wfDebug( "RC: cached feed timestamp check failed ($feedLastmod; $lastmod)\n" ); + } + } + return false; + } + + /** + * Generate the feed items given a row from the database, printing the feed. + * @param object $rows DatabaseBase resource with recentchanges rows + * @param Feed $feed + */ + public static function generateFeed( $rows, &$feed ) { + wfProfileIn( __METHOD__ ); + $items = self::buildItems( $rows ); + $feed->outHeader(); + foreach ( $items as $item ) { + $feed->outItem( $item ); + } + $feed->outFooter(); + wfProfileOut( __METHOD__ ); + } + + /** + * Generate the feed items given a row from the database. + * @param object $rows DatabaseBase resource with recentchanges rows + * @return array + */ + public static function buildItems( $rows ) { + wfProfileIn( __METHOD__ ); + $items = array(); + + # Merge adjacent edits by one user + $sorted = array(); + $n = 0; + foreach ( $rows as $obj ) { + if ( $n > 0 && + $obj->rc_type == RC_EDIT && + $obj->rc_namespace >= 0 && + $obj->rc_cur_id == $sorted[$n - 1]->rc_cur_id && + $obj->rc_user_text == $sorted[$n - 1]->rc_user_text ) { + $sorted[$n - 1]->rc_last_oldid = $obj->rc_last_oldid; + } else { + $sorted[$n] = $obj; + $n++; + } + } + + foreach ( $sorted as $obj ) { + $title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title ); + $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) + ? $title->getTalkPage()->getFullURL() + : ''; + + // Skip items with deleted content (avoids partially complete/inconsistent output) + if ( $obj->rc_deleted ) { + continue; + } + + if ( $obj->rc_this_oldid ) { + $url = $title->getFullURL( array( + 'diff' => $obj->rc_this_oldid, + 'oldid' => $obj->rc_last_oldid, + ) ); + } else { + // log entry or something like that. + $url = $title->getFullURL(); + } + + $items[] = new FeedItem( + $title->getPrefixedText(), + FeedUtils::formatDiff( $obj ), + $url, + $obj->rc_timestamp, + ( $obj->rc_deleted & Revision::DELETED_USER ) + ? wfMessage( 'rev-deleted-user' )->escaped() : $obj->rc_user_text, + $talkpage + ); + } + + wfProfileOut( __METHOD__ ); + return $items; + } +} diff --git a/includes/changes/ChangesList.php b/includes/changes/ChangesList.php index bf800c46..03d1289f 100644 --- a/includes/changes/ChangesList.php +++ b/includes/changes/ChangesList.php @@ -23,20 +23,26 @@ */ class ChangesList extends ContextSource { - /** * @var Skin */ public $skin; protected $watchlist = false; - + protected $lastdate; protected $message; + protected $rc_cache; + protected $rcCacheIndex; + protected $rclistOpen; + protected $rcMoveIndex; + + /** @var MapCacheLRU */ + protected $watchingCache; /** * Changeslist constructor * - * @param $obj Skin or IContextSource + * @param Skin|IContextSource $obj */ public function __construct( $obj ) { if ( $obj instanceof IContextSource ) { @@ -47,27 +53,15 @@ class ChangesList extends ContextSource { $this->skin = $obj; } $this->preCacheMessages(); - } - - /** - * Fetch an appropriate changes list class for the main context - * This first argument used to be an User object. - * - * @deprecated in 1.18; use newFromContext() instead - * @param string|User $unused Unused - * @return ChangesList|EnhancedChangesList|OldChangesList derivative - */ - public static function newFromUser( $unused ) { - wfDeprecated( __METHOD__, '1.18' ); - return self::newFromContext( RequestContext::getMain() ); + $this->watchingCache = new MapCacheLRU( 50 ); } /** * Fetch an appropriate changes list class for the specified context * Some users might want to use an enhanced list format, for instance * - * @param $context IContextSource to use - * @return ChangesList|EnhancedChangesList|OldChangesList derivative + * @param IContextSource $context + * @return ChangesList */ public static function newFromContext( IContextSource $context ) { $user = $context->getUser(); @@ -75,6 +69,7 @@ class ChangesList extends ContextSource { $list = null; if ( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) { $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) ); + return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context ); } else { return $list; @@ -83,13 +78,21 @@ class ChangesList extends ContextSource { /** * Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag - * @param $value Boolean + * @param bool $value */ public function setWatchlistDivs( $value = true ) { $this->watchlist = $value; } /** + * @return bool True when setWatchlistDivs has been called + * @since 1.23 + */ + public function isWatchlist() { + return (bool)$this->watchlist; + } + + /** * As we use the same small set of messages in various methods and that * they are called often, we call them once and save them in $this->message */ @@ -107,17 +110,17 @@ class ChangesList extends ContextSource { /** * Returns the appropriate flags for new page, minor change and patrolling * @param array $flags Associative array of 'flag' => Bool - * @param string $nothing to use for empty space - * @return String + * @param string $nothing To use for empty space + * @return string */ public function recentChangesFlags( $flags, $nothing = ' ' ) { - global $wgRecentChangesFlags; $f = ''; - foreach ( array_keys( $wgRecentChangesFlags ) as $flag ) { + foreach ( array_keys( $this->getConfig()->get( 'RecentChangesFlags' ) ) as $flag ) { $f .= isset( $flags[$flag] ) && $flags[$flag] ? self::flag( $flag ) : $nothing; } + return $f; } @@ -128,7 +131,7 @@ class ChangesList extends ContextSource { * "!" respectively, plus it will have an appropriate title and class. * * @param string $flag One key of $wgRecentChangesFlags - * @return String: Raw HTML + * @return string Raw HTML */ public static function flag( $flag ) { static $flagInfos = null; @@ -153,14 +156,14 @@ class ChangesList extends ContextSource { $flag = $map[$flag]; } - return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" . $flagInfos[$flag]['title'] . "'>" . - $flagInfos[$flag]['letter'] . + return "<abbr class='" . $flagInfos[$flag]['class'] . "' title='" . + $flagInfos[$flag]['title'] . "'>" . $flagInfos[$flag]['letter'] . '</abbr>'; } /** * Returns text for the start of the tabular part of RC - * @return String + * @return string */ public function beginRecentChangesList() { $this->rc_cache = array(); @@ -169,19 +172,25 @@ class ChangesList extends ContextSource { $this->lastdate = ''; $this->rclistOpen = false; $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' ); - return ''; + + return '<div class="mw-changeslist">'; + } + + /** + * @param ResultWrapper|array $rows + */ + public function initChangesListRows( $rows ) { + wfRunHooks( 'ChangesListInitRows', array( $this, $rows ) ); } /** * Show formatted char difference - * @param $old Integer: bytes - * @param $new Integer: bytes - * @param $context IContextSource context to use - * @return String + * @param int $old Number of bytes + * @param int $new Number of bytes + * @param IContextSource $context + * @return string */ public static function showCharacterDifference( $old, $new, IContextSource $context = null ) { - global $wgRCChangedSizeThreshold, $wgMiserMode; - if ( !$context ) { $context = RequestContext::getMain(); } @@ -191,10 +200,11 @@ class ChangesList extends ContextSource { $szdiff = $new - $old; $lang = $context->getLanguage(); + $config = $context->getConfig(); $code = $lang->getCode(); static $fastCharDiff = array(); if ( !isset( $fastCharDiff[$code] ) ) { - $fastCharDiff[$code] = $wgMiserMode || $context->msg( 'rc-change-size' )->plain() === '$1'; + $fastCharDiff[$code] = $config->get( 'MiserMode' ) || $context->msg( 'rc-change-size' )->plain() === '$1'; } $formattedSize = $lang->formatNum( $szdiff ); @@ -203,7 +213,7 @@ class ChangesList extends ContextSource { $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text(); } - if ( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) { + if ( abs( $szdiff ) > abs( $config->get( 'RCChangedSizeThreshold' ) ) ) { $tag = 'strong'; } else { $tag = 'span'; @@ -211,12 +221,10 @@ class ChangesList extends ContextSource { if ( $szdiff === 0 ) { $formattedSizeClass = 'mw-plusminus-null'; - } - if ( $szdiff > 0 ) { + } elseif ( $szdiff > 0 ) { $formattedSize = '+' . $formattedSize; $formattedSizeClass = 'mw-plusminus-pos'; - } - if ( $szdiff < 0 ) { + } else { $formattedSizeClass = 'mw-plusminus-neg'; } @@ -230,8 +238,8 @@ class ChangesList extends ContextSource { /** * Format the character difference of one or several changes. * - * @param $old RecentChange - * @param $new RecentChange last change to use, if not provided, $old will be used + * @param RecentChange $old + * @param RecentChange $new Last change to use, if not provided, $old will be used * @return string HTML fragment */ public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) { @@ -252,19 +260,18 @@ class ChangesList extends ContextSource { /** * Returns text for the end of RC - * @return String + * @return string */ public function endRecentChangesList() { - if ( $this->rclistOpen ) { - return "</ul>\n"; - } else { - return ''; - } + $out = $this->rclistOpen ? "</ul>\n" : ''; + $out .= '</div>'; + + return $out; } /** * @param string $s HTML to update - * @param $rc_timestamp mixed + * @param mixed $rc_timestamp */ public function insertDateHeader( &$s, $rc_timestamp ) { # Make date header if necessary @@ -281,8 +288,8 @@ class ChangesList extends ContextSource { /** * @param string $s HTML to update - * @param $title Title - * @param $logtype string + * @param Title $title + * @param string $logtype */ public function insertLog( &$s, $title, $logtype ) { $page = new LogPage( $logtype ); @@ -292,8 +299,8 @@ class ChangesList extends ContextSource { /** * @param string $s HTML to update - * @param $rc RecentChange - * @param $unpatrolled + * @param RecentChange $rc + * @param bool $unpatrolled */ public function insertDiffHist( &$s, &$rc, $unpatrolled ) { # Diff link @@ -326,17 +333,22 @@ class ChangesList extends ContextSource { 'action' => 'history' ) ); - $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . ' <span class="mw-changeslist-separator">. .</span> '; + // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? + $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() . + ' <span class="mw-changeslist-separator">. .</span> '; } /** * @param string $s HTML to update - * @param $rc RecentChange - * @param $unpatrolled - * @param $watched + * @param RecentChange $rc + * @param bool $unpatrolled + * @param bool $watched */ public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) { $params = array(); + if ( $rc->getTitle()->isRedirect() ) { + $params = array( 'redirect' => 'no' ); + } $articlelink = Linker::linkKnown( $rc->getTitle(), @@ -362,19 +374,23 @@ class ChangesList extends ContextSource { * Get the timestamp from $rc formatted with current user's settings * and a separator * - * @param $rc RecentChange + * @param RecentChange $rc * @return string HTML fragment */ public function getTimestamp( $rc ) { + // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? return $this->message['semicolon-separator'] . '<span class="mw-changeslist-date">' . - $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() ) . '</span> <span class="mw-changeslist-separator">. .</span> '; + $this->getLanguage()->userTime( + $rc->mAttribs['rc_timestamp'], + $this->getUser() + ) . '</span> <span class="mw-changeslist-separator">. .</span> '; } /** * Insert time timestamp string from $rc into $s * * @param string $s HTML to update - * @param $rc RecentChange + * @param RecentChange $rc */ public function insertTimestamp( &$s, $rc ) { $s .= $this->getTimestamp( $rc ); @@ -383,12 +399,13 @@ class ChangesList extends ContextSource { /** * Insert links to user page, user talk page and eventually a blocking link * - * @param &$s String HTML to update - * @param &$rc RecentChange + * @param string &$s HTML to update + * @param RecentChange &$rc */ public function insertUserRelatedLinks( &$s, &$rc ) { if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) { - $s .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; + $s .= ' <span class="history-deleted">' . + $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; } else { $s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); @@ -399,7 +416,7 @@ class ChangesList extends ContextSource { /** * Insert a formatted action * - * @param $rc RecentChange + * @param RecentChange $rc * @return string */ public function insertLogEntry( $rc ) { @@ -407,30 +424,29 @@ class ChangesList extends ContextSource { $formatter->setContext( $this->getContext() ); $formatter->setShowUserToolLinks( true ); $mark = $this->getLanguage()->getDirMark(); + return $formatter->getActionText() . " $mark" . $formatter->getComment(); } /** * Insert a formatted comment - * @param $rc RecentChange + * @param RecentChange $rc * @return string */ public function insertComment( $rc ) { - if ( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) { - if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) { - return ' <span class="history-deleted">' . $this->msg( 'rev-deleted-comment' )->escaped() . '</span>'; - } else { - return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); - } + if ( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) { + return ' <span class="history-deleted">' . + $this->msg( 'rev-deleted-comment' )->escaped() . '</span>'; + } else { + return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); } - return ''; } /** * Check whether to enable recent changes patrol features * * @deprecated since 1.22 - * @return Boolean + * @return bool */ public static function usePatrol() { global $wgUser; @@ -442,15 +458,18 @@ class ChangesList extends ContextSource { /** * Returns the string which indicates the number of watching users + * @param int $count Number of user watching a page * @return string */ protected function numberofWatchingusers( $count ) { - static $cache = array(); + $cache = $this->watchingCache; if ( $count > 0 ) { - if ( !isset( $cache[$count] ) ) { - $cache[$count] = $this->msg( 'number_of_watching_users_RCview' )->numParams( $count )->escaped(); + if ( !$cache->has( $count ) ) { + $cache->set( $count, $this->msg( 'number_of_watching_users_RCview' ) + ->numParams( $count )->escaped() ); } - return $cache[$count]; + + return $cache->get( $count ); } else { return ''; } @@ -458,9 +477,9 @@ class ChangesList extends ContextSource { /** * Determine if said field of a revision is hidden - * @param $rc RCCacheEntry - * @param $field Integer: one of DELETED_* bitfield constants - * @return Boolean + * @param RCCacheEntry|RecentChange $rc + * @param int $field One of DELETED_* bitfield constants + * @return bool */ public static function isDeleted( $rc, $field ) { return ( $rc->mAttribs['rc_deleted'] & $field ) == $field; @@ -469,10 +488,10 @@ class ChangesList extends ContextSource { /** * Determine if the current user is allowed to view a particular * field of this revision, if it's marked as deleted. - * @param $rc RCCacheEntry - * @param $field Integer - * @param $user User object to check, or null to use $wgUser - * @return Boolean + * @param RCCacheEntry|RecentChange $rc + * @param int $field + * @param User $user User object to check, or null to use $wgUser + * @return bool */ public static function userCan( $rc, $field, User $user = null ) { if ( $rc->mAttribs['rc_type'] == RC_LOG ) { @@ -483,8 +502,8 @@ class ChangesList extends ContextSource { } /** - * @param $link string - * @param $watched bool + * @param string $link + * @param bool $watched * @return string */ protected function maybeWatchedLink( $link, $watched = false ) { @@ -497,16 +516,20 @@ class ChangesList extends ContextSource { /** Inserts a rollback link * - * @param $s string - * @param $rc RecentChange + * @param string $s + * @param RecentChange $rc */ public function insertRollback( &$s, &$rc ) { - if ( $rc->mAttribs['rc_type'] == RC_EDIT && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) { + if ( $rc->mAttribs['rc_type'] == RC_EDIT + && $rc->mAttribs['rc_this_oldid'] + && $rc->mAttribs['rc_cur_id'] + ) { $page = $rc->getTitle(); /** Check for rollback and edit permissions, disallow special pages, and only - * show a link on the top-most revision */ - if ( $this->getUser()->isAllowed( 'rollback' ) && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] ) - { + * show a link on the top-most revision */ + if ( $this->getUser()->isAllowed( 'rollback' ) + && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] + ) { $rev = new Revision( array( 'title' => $page, 'id' => $rc->mAttribs['rc_this_oldid'], @@ -520,16 +543,19 @@ class ChangesList extends ContextSource { } /** - * @param $s string - * @param $rc RecentChange - * @param $classes + * @param string $s + * @param RecentChange $rc + * @param array $classes */ public function insertTags( &$s, &$rc, &$classes ) { if ( empty( $rc->mAttribs['ts_tags'] ) ) { return; } - list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' ); + list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( + $rc->mAttribs['ts_tags'], + 'changeslist' + ); $classes = array_merge( $classes, $newClasses ); $s .= ' ' . $tagSummary; } @@ -539,14 +565,32 @@ class ChangesList extends ContextSource { } protected function showAsUnpatrolled( RecentChange $rc ) { - $unpatrolled = false; - if ( !$rc->mAttribs['rc_patrolled'] ) { - if ( $this->getUser()->useRCPatrol() ) { - $unpatrolled = true; - } elseif ( $this->getUser()->useNPPatrol() && $rc->mAttribs['rc_type'] == RC_NEW ) { - $unpatrolled = true; + return self::isUnpatrolled( $rc, $this->getUser() ); + } + + /** + * @param object|RecentChange $rc Database row from recentchanges or a RecentChange object + * @param User $user + * @return bool + */ + public static function isUnpatrolled( $rc, User $user ) { + if ( $rc instanceof RecentChange ) { + $isPatrolled = $rc->mAttribs['rc_patrolled']; + $rcType = $rc->mAttribs['rc_type']; + } else { + $isPatrolled = $rc->rc_patrolled; + $rcType = $rc->rc_type; + } + + if ( !$isPatrolled ) { + if ( $user->useRCPatrol() ) { + return true; + } + if ( $user->useNPPatrol() && $rcType == RC_NEW ) { + return true; } } - return $unpatrolled; + + return false; } } diff --git a/includes/changes/EnhancedChangesList.php b/includes/changes/EnhancedChangesList.php index 4c8aa451..4ab77297 100644 --- a/includes/changes/EnhancedChangesList.php +++ b/includes/changes/EnhancedChangesList.php @@ -22,11 +22,44 @@ class EnhancedChangesList extends ChangesList { + /** + * @var RCCacheEntryFactory + */ + protected $cacheEntryFactory; + + /** + * @var array Array of array of RCCacheEntry + */ protected $rc_cache; /** + * @param IContextSource|Skin $obj + */ + public function __construct( $obj ) { + if ( $obj instanceof Skin ) { + // @todo: deprecate constructing with Skin + $context = $obj->getContext(); + } else { + if ( !$obj instanceof IContextSource ) { + throw new MWException( 'EnhancedChangesList must be constructed with a ' + . 'context source or skin.' ); + } + + $context = $obj; + } + + parent::__construct( $context ); + + // message is set by the parent ChangesList class + $this->cacheEntryFactory = new RCCacheEntryFactory( + $context, + $this->message + ); + } + + /** * Add the JavaScript file for enhanced changeslist - * @return String + * @return string */ public function beginRecentChangesList() { $this->rc_cache = array(); @@ -42,27 +75,29 @@ class EnhancedChangesList extends ChangesList { 'jquery.makeCollapsible', 'mediawiki.icon', ) ); - return ''; + + return '<div class="mw-changeslist">'; } + /** * Format a line for enhanced recentchange (aka with javascript and block of lines). * - * @param $baseRC RecentChange - * @param $watched bool + * @param RecentChange $baseRC + * @param bool $watched * * @return string */ public function recentChangesLine( &$baseRC, $watched = false ) { wfProfileIn( __METHOD__ ); - # Create a specialised object - $rc = RCCacheEntry::newFromParent( $baseRC ); + $date = $this->getLanguage()->userDate( + $baseRC->mAttribs['rc_timestamp'], + $this->getUser() + ); - $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] ); + $ret = ''; # If it's a new day, add the headline and flush the cache - $date = $this->getLanguage()->userDate( $rc->mAttribs['rc_timestamp'], $this->getUser() ); - $ret = ''; if ( $date != $this->lastdate ) { # Process current cache $ret = $this->recentChangesBlock(); @@ -71,128 +106,60 @@ class EnhancedChangesList extends ChangesList { $this->lastdate = $date; } - # Should patrol-related stuff be shown? - $rc->unpatrolled = $this->showAsUnpatrolled( $rc ); - - $showdifflinks = true; - # Make article link - $type = $rc->mAttribs['rc_type']; - $logType = $rc->mAttribs['rc_log_type']; - // Page moves, very old style, not supported anymore - if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { - // New unpatrolled pages - } elseif ( $rc->unpatrolled && $type == RC_NEW ) { - $clink = Linker::linkKnown( $rc->getTitle() ); - // Log entries - } elseif ( $type == RC_LOG ) { - if ( $logType ) { - $logtitle = SpecialPage::getTitleFor( 'Log', $logType ); - $logpage = new LogPage( $logType ); - $logname = $logpage->getName()->escaped(); - $clink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped(); - } else { - $clink = Linker::link( $rc->getTitle() ); - } - $watched = false; - // Log entries (old format) and special pages - } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - wfDebug( "Unexpected special page in recentchanges\n" ); - $clink = ''; - // Edits - } else { - $clink = Linker::linkKnown( $rc->getTitle() ); - } + $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $baseRC, $watched ); + $this->addCacheEntry( $cacheEntry ); - # Don't show unusable diff links - if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) { - $showdifflinks = false; - } + wfProfileOut( __METHOD__ ); - $time = $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() ); - $rc->watched = $watched; - $rc->link = $clink; - $rc->timestamp = $time; - $rc->numberofWatchingusers = $baseRC->numberofWatchingusers; - - # Make "cur" and "diff" links. Do not use link(), it is too slow if - # called too many times (50% of CPU time on RecentChanges!). - $thisOldid = $rc->mAttribs['rc_this_oldid']; - $lastOldid = $rc->mAttribs['rc_last_oldid']; - - $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid ); - $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ); - - if ( !$showdifflinks ) { - $curLink = $this->message['cur']; - $diffLink = $this->message['diff']; - } elseif ( in_array( $type, array( RC_NEW, RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) { - if ( $type != RC_NEW ) { - $curLink = $this->message['cur']; - } else { - $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) ); - $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>"; - } - $diffLink = $this->message['diff']; - } else { - $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) ); - $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) ); - $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>"; - $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>"; - } + return $ret; + } - # Make "last" link - if ( !$showdifflinks || !$lastOldid ) { - $lastLink = $this->message['last']; - } elseif ( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) { - $lastLink = $this->message['last']; - } else { - $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'], - array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) ); - } + /** + * Put accumulated information into the cache, for later display. + * Page moves go on their own line. + * + * @param RCCacheEntry $cacheEntry + */ + protected function addCacheEntry( RCCacheEntry $cacheEntry ) { + $cacheGroupingKey = $this->makeCacheGroupingKey( $cacheEntry ); - # Make user links - if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) { - $rc->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; - } else { - $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); - $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); + if ( !isset( $this->rc_cache[$cacheGroupingKey] ) ) { + $this->rc_cache[$cacheGroupingKey] = array(); } - $rc->lastlink = $lastLink; - $rc->curlink = $curLink; - $rc->difflink = $diffLink; - - # Put accumulated information into the cache, for later display - # Page moves go on their own line - $title = $rc->getTitle(); - $secureName = $title->getPrefixedDBkey(); - if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { - # Use an @ character to prevent collision with page names - $this->rc_cache['@@' . ( $this->rcMoveIndex++ )] = array( $rc ); - } else { - # Logs are grouped by type - if ( $type == RC_LOG ) { - $secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey(); - } - if ( !isset( $this->rc_cache[$secureName] ) ) { - $this->rc_cache[$secureName] = array(); - } + array_push( $this->rc_cache[$cacheGroupingKey], $cacheEntry ); + } - array_push( $this->rc_cache[$secureName], $rc ); - } + /** + * @todo use rc_source to group, if set; fallback to rc_type + * + * @param RCCacheEntry $cacheEntry + * + * @return string + */ + protected function makeCacheGroupingKey( RCCacheEntry $cacheEntry ) { + $title = $cacheEntry->getTitle(); + $cacheGroupingKey = $title->getPrefixedDBkey(); - wfProfileOut( __METHOD__ ); + $type = $cacheEntry->mAttribs['rc_type']; - return $ret; + if ( $type == RC_LOG ) { + // Group by log type + $cacheGroupingKey = SpecialPage::getTitleFor( + 'Log', + $cacheEntry->mAttribs['rc_log_type'] + )->getPrefixedDBkey(); + } + + return $cacheGroupingKey; } /** * Enhanced RC group + * @param RCCacheEntry[] $block * @return string */ protected function recentChangesBlockGroup( $block ) { - global $wgRCShowChangedSize; - wfProfileIn( __METHOD__ ); # Add the namespace and title of the block as part of the class @@ -203,7 +170,7 @@ class EnhancedChangesList extends ChangesList { . $block[0]->mAttribs['rc_log_type'] ); } else { $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' - . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); + . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); } $classes[] = $block[0]->watched && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; @@ -221,6 +188,8 @@ class EnhancedChangesList extends ChangesList { # Some catalyst variables... $namehidden = true; $allLogs = true; + $oldid = ''; + $RCShowChangedSize = $this->getConfig()->get( 'RCShowChangedSize' ); foreach ( $block as $rcObj ) { $oldid = $rcObj->mAttribs['rc_last_oldid']; if ( $rcObj->mAttribs['rc_type'] == RC_NEW ) { @@ -268,7 +237,9 @@ class EnhancedChangesList extends ChangesList { $text = $userlink; $text .= $this->getLanguage()->getDirMark(); if ( $count > 1 ) { - $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->formatNum( $count ) . '×' )->escaped(); + // @todo FIXME: Hardcoded '×'. Should be a message. + $formattedCount = $this->getLanguage()->formatNum( $count ) . '×'; + $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped(); } array_push( $users, $text ); } @@ -278,7 +249,8 @@ class EnhancedChangesList extends ChangesList { implode( $this->message['semicolon-separator'], $users ) )->escaped() . '</span>'; - $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>'; + $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow ' . + 'mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>'; $r .= "<td>$tl</td>"; # Main line @@ -294,7 +266,8 @@ class EnhancedChangesList extends ChangesList { # Article link if ( $namehidden ) { - $r .= ' <span class="history-deleted">' . $this->msg( 'rev-deleted-event' )->escaped() . '</span>'; + $r .= ' <span class="history-deleted">' . + $this->msg( 'rev-deleted-event' )->escaped() . '</span>'; } elseif ( $allLogs ) { $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); } else { @@ -316,6 +289,7 @@ class EnhancedChangesList extends ChangesList { $sinceLast = 0; $unvisitedOldid = null; + /** @var $rcObj RCCacheEntry */ foreach ( $block as $rcObj ) { // Same logic as below inside main foreach if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) { @@ -331,6 +305,8 @@ class EnhancedChangesList extends ChangesList { # Total change link $r .= ' '; $logtext = ''; + /** @var $block0 RecentChange */ + $block0 = $block[0]; if ( !$allLogs ) { if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) { $logtext .= $nchanges[$n]; @@ -338,7 +314,7 @@ class EnhancedChangesList extends ChangesList { $logtext .= $nchanges[$n]; } else { $logtext .= Linker::link( - $block[0]->getTitle(), + $block0->getTitle(), $nchanges[$n], array(), $queryParams + array( @@ -349,7 +325,7 @@ class EnhancedChangesList extends ChangesList { ); if ( $sinceLast > 0 && $sinceLast < $n ) { $logtext .= $this->message['pipe-separator'] . Linker::link( - $block[0]->getTitle(), + $block0->getTitle(), $sinceLastVisitMsg[$sinceLast], array(), $queryParams + array( @@ -365,7 +341,7 @@ class EnhancedChangesList extends ChangesList { # History if ( $allLogs ) { // don't show history link for logs - } elseif ( $namehidden || !$block[0]->getTitle()->exists() ) { + } elseif ( $namehidden || !$block0->getTitle()->exists() ) { $logtext .= $this->message['pipe-separator'] . $this->message['enhancedrc-history']; } else { $params = $queryParams; @@ -373,7 +349,7 @@ class EnhancedChangesList extends ChangesList { $logtext .= $this->message['pipe-separator'] . Linker::linkKnown( - $block[0]->getTitle(), + $block0->getTitle(), $this->message['enhancedrc-history'], array(), $params @@ -387,7 +363,7 @@ class EnhancedChangesList extends ChangesList { $r .= ' <span class="mw-changeslist-separator">. .</span> '; # Character difference (does not apply if only log items) - if ( $wgRCShowChangedSize && !$allLogs ) { + if ( $RCShowChangedSize && !$allLogs ) { $last = 0; $first = count( $block ) - 1; # Some events (like logs) have an "empty" size, so we need to skip those... @@ -408,7 +384,8 @@ class EnhancedChangesList extends ChangesList { } $r .= $users; - $r .= $this->numberofWatchingusers( $block[0]->numberofWatchingusers ); + $r .= $this->numberofWatchingusers( $block0->numberofWatchingusers ); + $r .= '</td></tr>'; # Sub-entries foreach ( $block as $rcObj ) { @@ -443,11 +420,11 @@ class EnhancedChangesList extends ChangesList { } else { $link = Linker::linkKnown( - $rcObj->getTitle(), - $rcObj->timestamp, - array(), - $params - ); + $rcObj->getTitle(), + $rcObj->timestamp, + array(), + $params + ); if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) { $link = '<span class="history-deleted">' . $link . '</span> '; } @@ -455,12 +432,16 @@ class EnhancedChangesList extends ChangesList { $r .= $link . '</span>'; if ( !$type == RC_LOG || $type == RC_NEW ) { - $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->curlink . $this->message['pipe-separator'] . $rcObj->lastlink )->escaped(); + $r .= ' ' . $this->msg( 'parentheses' )->rawParams( + $rcObj->curlink . + $this->message['pipe-separator'] . + $rcObj->lastlink + )->escaped(); } $r .= ' <span class="mw-changeslist-separator">. .</span> '; # Character diff - if ( $wgRCShowChangedSize ) { + if ( $RCShowChangedSize ) { $cd = $this->formatCharacterDifference( $rcObj ); if ( $cd !== '' ) { $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; @@ -493,56 +474,12 @@ class EnhancedChangesList extends ChangesList { } /** - * Generate HTML for an arrow or placeholder graphic - * @param string $dir one of '', 'd', 'l', 'r' - * @param string $alt text - * @param string $title text - * @return String: HTML "<img>" tag - */ - protected function arrow( $dir, $alt = '', $title = '' ) { - global $wgStylePath; - $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' ); - $encAlt = htmlspecialchars( $alt ); - $encTitle = htmlspecialchars( $title ); - return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />"; - } - - /** - * Generate HTML for a right- or left-facing arrow, - * depending on language direction. - * @return String: HTML "<img>" tag - */ - protected function sideArrow() { - $dir = $this->getLanguage()->isRTL() ? 'l' : 'r'; - return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() ); - } - - /** - * Generate HTML for a down-facing arrow - * depending on language direction. - * @return String: HTML "<img>" tag - */ - protected function downArrow() { - return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() ); - } - - /** - * Generate HTML for a spacer image - * @return String: HTML "<img>" tag - */ - protected function spacerArrow() { - return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space - } - - /** * Enhanced RC ungrouped line. * - * @param $rcObj RecentChange - * @return String: a HTML formatted line (generated using $r) + * @param RecentChange|RCCacheEntry $rcObj + * @return string A HTML formatted line (generated using $r) */ protected function recentChangesBlockLine( $rcObj ) { - global $wgRCShowChangedSize; - wfProfileIn( __METHOD__ ); $query['curid'] = $rcObj->mAttribs['rc_cur_id']; @@ -551,10 +488,10 @@ class EnhancedChangesList extends ChangesList { $classes = array( 'mw-enhanced-rc' ); if ( $logType ) { # Log entry -+ $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType ); + $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType ); } else { $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' . - $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] ); + $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] ); } $classes[] = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; @@ -563,39 +500,37 @@ class EnhancedChangesList extends ChangesList { $r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>'; # Flag and Timestamp - if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { - $r .= $this->recentChangesFlags( array() ); // no flags, but need the placeholders - } else { - $r .= $this->recentChangesFlags( array( - 'newpage' => $type == RC_NEW, - 'minor' => $rcObj->mAttribs['rc_minor'], - 'unpatrolled' => $rcObj->unpatrolled, - 'bot' => $rcObj->mAttribs['rc_bot'], - ) ); - } + $r .= $this->recentChangesFlags( array( + 'newpage' => $type == RC_NEW, + 'minor' => $rcObj->mAttribs['rc_minor'], + 'unpatrolled' => $rcObj->unpatrolled, + 'bot' => $rcObj->mAttribs['rc_bot'], + ) ); $r .= ' ' . $rcObj->timestamp . ' </td><td>'; # Article or log link if ( $logType ) { $logPage = new LogPage( $logType ); $logTitle = SpecialPage::getTitleFor( 'Log', $logType ); $logName = $logPage->getName()->escaped(); - $r .= $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped(); + $r .= $this->msg( 'parentheses' ) + ->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped(); } else { $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched ); } # Diff and hist links if ( $type != RC_LOG ) { $query['action'] = 'history'; - $r .= ' ' . $this->msg( 'parentheses' )->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown( - $rcObj->getTitle(), - $this->message['hist'], - array(), - $query - ) )->escaped(); + $r .= ' ' . $this->msg( 'parentheses' ) + ->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown( + $rcObj->getTitle(), + $this->message['hist'], + array(), + $query + ) )->escaped(); } $r .= ' <span class="mw-changeslist-separator">. .</span> '; # Character diff - if ( $wgRCShowChangedSize ) { + if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) { $cd = $this->formatCharacterDifference( $rcObj ); if ( $cd !== '' ) { $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; @@ -629,7 +564,7 @@ class EnhancedChangesList extends ChangesList { * @return string */ protected function recentChangesBlock() { - if ( count ( $this->rc_cache ) == 0 ) { + if ( count( $this->rc_cache ) == 0 ) { return ''; } @@ -655,7 +590,6 @@ class EnhancedChangesList extends ChangesList { * @return string */ public function endRecentChangesList() { - return $this->recentChangesBlock() . parent::endRecentChangesList(); + return $this->recentChangesBlock() . '</div>'; } - } diff --git a/includes/changes/OldChangesList.php b/includes/changes/OldChangesList.php index a7fe9342..4eed9262 100644 --- a/includes/changes/OldChangesList.php +++ b/includes/changes/OldChangesList.php @@ -19,28 +19,21 @@ * * @file */ + class OldChangesList extends ChangesList { /** * Format a line using the old system (aka without any javascript). * - * @param $rc RecentChange, passed by reference + * @param RecentChange $rc Passed by reference * @param bool $watched (default false) * @param int $linenumber (default null) * * @return string|bool */ public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) { - global $wgRCShowChangedSize; wfProfileIn( __METHOD__ ); - # Should patrol-related stuff be shown? - $unpatrolled = $this->showAsUnpatrolled( $rc ); - - $dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience. - $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); - - $s = ''; $classes = array(); // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468) if ( $linenumber ) { @@ -56,23 +49,53 @@ class OldChangesList extends ChangesList { $classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; - // Moved pages (very very old, not supported anymore) - if ( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) { - // Log entries - } elseif ( $rc->mAttribs['rc_log_type'] ) { + $html = $this->formatChangeLine( $rc, $classes, $watched ); + + if ( $this->watchlist ) { + $classes[] = Sanitizer::escapeClass( 'watchlist-' . + $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); + } + + if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$html, $rc, &$classes ) ) ) { + wfProfileOut( __METHOD__ ); + + return false; + } + + wfProfileOut( __METHOD__ ); + + $dateheader = ''; // $html now contains only <li>...</li>, for hooks' convenience. + $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); + + return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $html . "</li>\n"; + } + + /** + * @param RecentChange $rc + * @param string[] &$classes + * @param boolean $watched + * + * @return string + */ + private function formatChangeLine( RecentChange $rc, array &$classes, $watched ) { + $html = ''; + + if ( $rc->mAttribs['rc_log_type'] ) { $logtitle = SpecialPage::getTitleFor( 'Log', $rc->mAttribs['rc_log_type'] ); - $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] ); + $this->insertLog( $html, $logtitle, $rc->mAttribs['rc_log_type'] ); // Log entries (old format) or log targets, and special pages } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) { - list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); + list( $name, $htmlubpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] ); if ( $name == 'Log' ) { - $this->insertLog( $s, $rc->getTitle(), $subpage ); + $this->insertLog( $html, $rc->getTitle(), $htmlubpage ); } // Regular entries } else { - $this->insertDiffHist( $s, $rc, $unpatrolled ); + $unpatrolled = $this->showAsUnpatrolled( $rc ); + + $this->insertDiffHist( $html, $rc, $unpatrolled ); # M, N, b and ! (minor, new, bot and unpatrolled) - $s .= $this->recentChangesFlags( + $html .= $this->recentChangesFlags( array( 'newpage' => $rc->mAttribs['rc_type'] == RC_NEW, 'minor' => $rc->mAttribs['rc_minor'], @@ -81,50 +104,40 @@ class OldChangesList extends ChangesList { ), '' ); - $this->insertArticleLink( $s, $rc, $unpatrolled, $watched ); + $this->insertArticleLink( $html, $rc, $unpatrolled, $watched ); } # Edit/log timestamp - $this->insertTimestamp( $s, $rc ); + $this->insertTimestamp( $html, $rc ); # Bytes added or removed - if ( $wgRCShowChangedSize ) { + if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) { $cd = $this->formatCharacterDifference( $rc ); if ( $cd !== '' ) { - $s .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; + $html .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; } } if ( $rc->mAttribs['rc_type'] == RC_LOG ) { - $s .= $this->insertLogEntry( $rc ); + $html .= $this->insertLogEntry( $rc ); } else { # User tool links - $this->insertUserRelatedLinks( $s, $rc ); + $this->insertUserRelatedLinks( $html, $rc ); # LTR/RTL direction mark - $s .= $this->getLanguage()->getDirMark(); - $s .= $this->insertComment( $rc ); + $html .= $this->getLanguage()->getDirMark(); + $html .= $this->insertComment( $rc ); } # Tags - $this->insertTags( $s, $rc, $classes ); + $this->insertTags( $html, $rc, $classes ); # Rollback - $this->insertRollback( $s, $rc ); + $this->insertRollback( $html, $rc ); # For subclasses - $this->insertExtra( $s, $rc, $classes ); + $this->insertExtra( $html, $rc, $classes ); # How many users watch this page if ( $rc->numberofWatchingusers > 0 ) { - $s .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers ); + $html .= ' ' . $this->numberofWatchingusers( $rc->numberofWatchingusers ); } - if ( $this->watchlist ) { - $classes[] = Sanitizer::escapeClass( 'watchlist-' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); - } - - if ( !wfRunHooks( 'OldChangesListRecentChangesLine', array( &$this, &$s, $rc, &$classes ) ) ) { - wfProfileOut( __METHOD__ ); - return false; - } - - wfProfileOut( __METHOD__ ); - return "$dateheader<li class=\"" . implode( ' ', $classes ) . "\">" . $s . "</li>\n"; + return $html; } } diff --git a/includes/changes/RCCacheEntry.php b/includes/changes/RCCacheEntry.php index 9aef3d30..d9cafbc3 100644 --- a/includes/changes/RCCacheEntry.php +++ b/includes/changes/RCCacheEntry.php @@ -17,19 +17,27 @@ * * @file */ + class RCCacheEntry extends RecentChange { - var $secureName, $link; - var $curlink, $difflink, $lastlink, $usertalklink, $versionlink; - var $userlink, $timestamp, $watched; + public $curlink; + public $difflink; + public $lastlink; + public $link; + public $timestamp; + public $unpatrolled; + public $userlink; + public $usertalklink; + public $watched; /** - * @param $rc RecentChange + * @param RecentChange $rc * @return RCCacheEntry */ static function newFromParent( $rc ) { $rc2 = new RCCacheEntry; $rc2->mAttribs = $rc->mAttribs; $rc2->mExtra = $rc->mExtra; + return $rc2; } } diff --git a/includes/changes/RCCacheEntryFactory.php b/includes/changes/RCCacheEntryFactory.php new file mode 100644 index 00000000..c3fe183e --- /dev/null +++ b/includes/changes/RCCacheEntryFactory.php @@ -0,0 +1,275 @@ +<?php +/** + * Creates a RCCacheEntry from a RecentChange to use in EnhancedChangesList + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +class RCCacheEntryFactory { + + /* @var IContextSource */ + private $context; + + /* @var string[] */ + private $messages; + + /** + * @param IContextSource $context + * @param string[] $messages + */ + public function __construct( IContextSource $context, $messages ) { + $this->context = $context; + $this->messages = $messages; + } + + /** + * @param RecentChange $baseRC + * @param bool $watched + * + * @return RCCacheEntry + */ + public function newFromRecentChange( RecentChange $baseRC, $watched ) { + $user = $this->context->getUser(); + $counter = $baseRC->counter; + + $cacheEntry = RCCacheEntry::newFromParent( $baseRC ); + + // Should patrol-related stuff be shown? + $cacheEntry->unpatrolled = ChangesList::isUnpatrolled( $baseRC, $user ); + + $cacheEntry->watched = $cacheEntry->mAttribs['rc_type'] == RC_LOG ? false : $watched; + $cacheEntry->numberofWatchingusers = $baseRC->numberofWatchingusers; + + $cacheEntry->link = $this->buildCLink( $cacheEntry ); + $cacheEntry->timestamp = $this->buildTimestamp( $cacheEntry ); + + // Make "cur" and "diff" links. Do not use link(), it is too slow if + // called too many times (50% of CPU time on RecentChanges!). + $showDiffLinks = $this->showDiffLinks( $cacheEntry, $user ); + + $cacheEntry->difflink = $this->buildDiffLink( $cacheEntry, $showDiffLinks, $counter ); + $cacheEntry->curlink = $this->buildCurLink( $cacheEntry, $showDiffLinks, $counter ); + $cacheEntry->lastlink = $this->buildLastLink( $cacheEntry, $showDiffLinks ); + + // Make user links + $cacheEntry->userlink = $this->getUserLink( $cacheEntry ); + + if ( !ChangesList::isDeleted( $cacheEntry, Revision::DELETED_USER ) ) { + $cacheEntry->usertalklink = Linker::userToolLinks( + $cacheEntry->mAttribs['rc_user'], + $cacheEntry->mAttribs['rc_user_text'] + ); + } + + return $cacheEntry; + } + + /** + * @param RecentChange $cacheEntry + * @param User $user + * + * @return bool + */ + private function showDiffLinks( RecentChange $cacheEntry, User $user ) { + return ChangesList::userCan( $cacheEntry, Revision::DELETED_TEXT, $user ); + } + + /** + * @param RecentChange $cacheEntry + * + * @return string + */ + private function buildCLink( RecentChange $cacheEntry ) { + $type = $cacheEntry->mAttribs['rc_type']; + + // New unpatrolled pages + if ( $cacheEntry->unpatrolled && $type == RC_NEW ) { + $clink = Linker::linkKnown( $cacheEntry->getTitle() ); + // Log entries + } elseif ( $type == RC_LOG ) { + $logType = $cacheEntry->mAttribs['rc_log_type']; + + if ( $logType ) { + $clink = $this->getLogLink( $logType ); + } else { + wfDebugLog( 'recentchanges', 'Unexpected log entry with no log type in recent changes' ); + $clink = Linker::link( $cacheEntry->getTitle() ); + } + // Log entries (old format) and special pages + } elseif ( $cacheEntry->mAttribs['rc_namespace'] == NS_SPECIAL ) { + wfDebugLog( 'recentchanges', 'Unexpected special page in recentchanges' ); + $clink = ''; + // Edits + } else { + $clink = Linker::linkKnown( $cacheEntry->getTitle() ); + } + + return $clink; + } + + private function getLogLink( $logType ) { + $logtitle = SpecialPage::getTitleFor( 'Log', $logType ); + $logpage = new LogPage( $logType ); + $logname = $logpage->getName()->escaped(); + + $logLink = $this->context->msg( 'parentheses' ) + ->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped(); + + return $logLink; + } + + /** + * @param RecentChange $cacheEntry + * + * @return string + */ + private function buildTimestamp( RecentChange $cacheEntry ) { + return $this->context->getLanguage()->userTime( + $cacheEntry->mAttribs['rc_timestamp'], + $this->context->getUser() + ); + } + + /** + * @param RecentChange $recentChange + * + * @return array + */ + private function buildCurQueryParams( RecentChange $recentChange ) { + return array( + 'curid' => $recentChange->mAttribs['rc_cur_id'], + 'diff' => 0, + 'oldid' => $recentChange->mAttribs['rc_this_oldid'] + ); + } + + /** + * @param RecentChange $cacheEntry + * @param bool $showDiffLinks + * @param int $counter + * + * @return string + */ + private function buildCurLink( RecentChange $cacheEntry, $showDiffLinks, $counter ) { + $queryParams = $this->buildCurQueryParams( $cacheEntry ); + $curMessage = $this->getMessage( 'cur' ); + $logTypes = array( RC_LOG ); + + if ( !$showDiffLinks || in_array( $cacheEntry->mAttribs['rc_type'], $logTypes ) ) { + $curLink = $curMessage; + } else { + $curUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) ); + $curLink = "<a href=\"$curUrl\" tabindex=\"$counter\">$curMessage</a>"; + } + + return $curLink; + } + + /** + * @param RecentChange $recentChange + * + * @return array + */ + private function buildDiffQueryParams( RecentChange $recentChange ) { + return array( + 'curid' => $recentChange->mAttribs['rc_cur_id'], + 'diff' => $recentChange->mAttribs['rc_this_oldid'], + 'oldid' => $recentChange->mAttribs['rc_last_oldid'] + ); + } + + /** + * @param RecentChange $cacheEntry + * @param bool $showDiffLinks + * @param int $counter + * + * @return string + */ + private function buildDiffLink( RecentChange $cacheEntry, $showDiffLinks, $counter ) { + $queryParams = $this->buildDiffQueryParams( $cacheEntry ); + $diffMessage = $this->getMessage( 'diff' ); + $logTypes = array( RC_NEW, RC_LOG ); + + if ( !$showDiffLinks ) { + $diffLink = $diffMessage; + } elseif ( in_array( $cacheEntry->mAttribs['rc_type'], $logTypes ) ) { + $diffLink = $diffMessage; + } else { + $diffUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) ); + $diffLink = "<a href=\"$diffUrl\" tabindex=\"$counter\">$diffMessage</a>"; + } + + return $diffLink; + } + + /** + * @param RecentChange $cacheEntry + * @param bool $showDiffLinks + * + * @return string + */ + private function buildLastLink( RecentChange $cacheEntry, $showDiffLinks ) { + $lastOldid = $cacheEntry->mAttribs['rc_last_oldid']; + $lastMessage = $this->getMessage( 'last' ); + $type = $cacheEntry->mAttribs['rc_type']; + $logTypes = array( RC_LOG ); + + // Make "last" link + if ( !$showDiffLinks || !$lastOldid || in_array( $type, $logTypes ) ) { + $lastLink = $lastMessage; + } else { + $lastLink = Linker::linkKnown( + $cacheEntry->getTitle(), + $lastMessage, + array(), + $this->buildDiffQueryParams( $cacheEntry ) + ); + } + + return $lastLink; + } + + /** + * @param RecentChange $cacheEntry + * + * @return string + */ + private function getUserLink( RecentChange $cacheEntry ) { + if ( ChangesList::isDeleted( $cacheEntry, Revision::DELETED_USER ) ) { + $userLink = ' <span class="history-deleted">' . + $this->context->msg( 'rev-deleted-user' )->escaped() . '</span>'; + } else { + $userLink = Linker::userLink( + $cacheEntry->mAttribs['rc_user'], + $cacheEntry->mAttribs['rc_user_text'] + ); + } + + return $userLink; + } + + /** + * @param string $key + * + * @return string + */ + private function getMessage( $key ) { + return $this->messages[$key]; + } + +} diff --git a/includes/changes/RecentChange.php b/includes/changes/RecentChange.php index 980bd0a0..e33274e8 100644 --- a/includes/changes/RecentChange.php +++ b/includes/changes/RecentChange.php @@ -26,10 +26,10 @@ * mAttribs: * rc_id id of the row in the recentchanges table * rc_timestamp time the entry was made - * rc_cur_time timestamp on the cur row * rc_namespace namespace # * rc_title non-prefixed db key * rc_type is new entry, used to determine whether updating is necessary + * rc_source string representation of change source * rc_minor is minor * rc_cur_id page_id of associated page entry * rc_user user id who made the entry @@ -52,7 +52,6 @@ * mExtra: * prefixedDBkey prefixed db key, used by external app via msg queue * lastTimestamp timestamp of previous entry, used in WHERE clause during update - * lang the interwiki prefix, automatically set in save() * oldSize text size before the change * newSize text size after the change * pageStatus status of the page: created, deleted, moved, restored, changed @@ -60,59 +59,111 @@ * temporary: not stored in the database * notificationtimestamp * numberofWatchingusers - * - * @todo document functions and variables */ class RecentChange { - var $mAttribs = array(), $mExtra = array(); + // Constants for the rc_source field. Extensions may also have + // their own source constants. + const SRC_EDIT = 'mw.edit'; + const SRC_NEW = 'mw.new'; + const SRC_LOG = 'mw.log'; + const SRC_EXTERNAL = 'mw.external'; // obsolete + + public $mAttribs = array(); + public $mExtra = array(); /** * @var Title */ - var $mTitle = false; + public $mTitle = false; /** * @var User */ private $mPerformer = false; + public $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentChangesLinked + public $notificationtimestamp; + /** - * @var Title + * @var int Line number of recent change. Default -1. */ - var $mMovedToTitle = false; - var $numberofWatchingusers = 0; # Dummy to prevent error message in SpecialRecentchangeslinked - var $notificationtimestamp; + public $counter = -1; # Factory methods /** - * @param $row + * @param mixed $row * @return RecentChange */ public static function newFromRow( $row ) { $rc = new RecentChange; $rc->loadFromRow( $row ); + return $rc; } /** - * @deprecated in 1.22 - * @param $row - * @return RecentChange + * Parsing text to RC_* constants + * @since 1.24 + * @param string|array $type + * @throws MWException + * @return int|array RC_TYPE */ - public static function newFromCurRow( $row ) { - wfDeprecated( __METHOD__, '1.22' ); - $rc = new RecentChange; - $rc->loadFromCurRow( $row ); - $rc->notificationtimestamp = false; - $rc->numberofWatchingusers = false; - return $rc; + public static function parseToRCType( $type ) { + if ( is_array( $type ) ) { + $retval = array(); + foreach ( $type as $t ) { + $retval[] = RecentChange::parseToRCType( $t ); + } + + return $retval; + } + + switch ( $type ) { + case 'edit': + return RC_EDIT; + case 'new': + return RC_NEW; + case 'log': + return RC_LOG; + case 'external': + return RC_EXTERNAL; + default: + throw new MWException( "Unknown type '$type'" ); + } + } + + /** + * Parsing RC_* constants to human-readable test + * @since 1.24 + * @param int $rcType + * @return string $type + */ + public static function parseFromRCType( $rcType ) { + switch ( $rcType ) { + case RC_EDIT: + $type = 'edit'; + break; + case RC_NEW: + $type = 'new'; + break; + case RC_LOG: + $type = 'log'; + break; + case RC_EXTERNAL: + $type = 'external'; + break; + default: + $type = "$rcType"; + } + + return $type; } /** * Obtain the recent change with a given rc_id value * - * @param int $rcid rc_id value to retrieve + * @param int $rcid The rc_id value to retrieve * @return RecentChange */ public static function newFromId( $rcid ) { @@ -122,9 +173,9 @@ class RecentChange { /** * Find the first recent change matching some specific conditions * - * @param array $conds of conditions - * @param $fname Mixed: override the method name in profiling/logs - * @param $options Array Query options + * @param array $conds Array of conditions + * @param mixed $fname Override the method name in profiling/logs + * @param array $options Query options * @return RecentChange */ public static function newFromConds( $conds, $fname = __METHOD__, $options = array() ) { @@ -146,7 +197,6 @@ class RecentChange { return array( 'rc_id', 'rc_timestamp', - 'rc_cur_time', 'rc_user', 'rc_user_text', 'rc_namespace', @@ -159,6 +209,7 @@ class RecentChange { 'rc_this_oldid', 'rc_last_oldid', 'rc_type', + 'rc_source', 'rc_patrolled', 'rc_ip', 'rc_old_len', @@ -174,27 +225,27 @@ class RecentChange { # Accessors /** - * @param $attribs array + * @param array $attribs */ public function setAttribs( $attribs ) { $this->mAttribs = $attribs; } /** - * @param $extra array + * @param array $extra */ public function setExtra( $extra ) { $this->mExtra = $extra; } /** - * * @return Title */ public function &getTitle() { if ( $this->mTitle === false ) { $this->mTitle = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] ); } + return $this->mTitle; } @@ -211,21 +262,21 @@ class RecentChange { $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false ); } } + return $this->mPerformer; } /** * Writes the data in this object to the database - * @param $noudp bool + * @param bool $noudp */ public function save( $noudp = false ) { - global $wgLocalInterwiki, $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang; + global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang; $dbw = wfGetDB( DB_MASTER ); if ( !is_array( $this->mExtra ) ) { $this->mExtra = array(); } - $this->mExtra['lang'] = $wgLocalInterwiki; if ( !$wgPutIPinRC ) { $this->mAttribs['rc_ip'] = ''; @@ -244,7 +295,6 @@ class RecentChange { # Fixup database timestamps $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] ); - $this->mAttribs['rc_cur_time'] = $dbw->timestamp( $this->mAttribs['rc_cur_time'] ); $this->mAttribs['rc_id'] = $dbw->nextSequenceValue( 'recentchanges_rc_id_seq' ); ## If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL @@ -271,7 +321,7 @@ class RecentChange { $editor = $this->getPerformer(); $title = $this->getTitle(); - if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title ) ) ) { + if ( wfRunHooks( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) { # @todo FIXME: This would be better as an extension hook $enotif = new EmailNotification(); $enotif->notifyOnPageChange( $editor, $title, @@ -285,44 +335,37 @@ class RecentChange { } /** - * @deprecated since 1.22, use notifyRCFeeds instead. - */ - public function notifyRC2UDP() { - wfDeprecated( __METHOD__, '1.22' ); - $this->notifyRCFeeds(); - } - - /** - * Send some text to UDP. - * @deprecated since 1.22 + * Notify all the feeds about the change. + * @param array $feeds Optional feeds to send to, defaults to $wgRCFeeds */ - public static function sendToUDP( $line, $address = '', $prefix = '', $port = '' ) { - global $wgRC2UDPAddress, $wgRC2UDPInterwikiPrefix, $wgRC2UDPPort, $wgRC2UDPPrefix; - - wfDeprecated( __METHOD__, '1.22' ); - - # Assume default for standard RC case - $address = $address ? $address : $wgRC2UDPAddress; - $prefix = $prefix ? $prefix : $wgRC2UDPPrefix; - $port = $port ? $port : $wgRC2UDPPort; + public function notifyRCFeeds( array $feeds = null ) { + global $wgRCFeeds; + if ( $feeds === null ) { + $feeds = $wgRCFeeds; + } - $engine = new UDPRCFeedEngine(); - $feed = array( - 'uri' => "udp://$address:$port/$prefix", - 'formatter' => 'IRCColourfulRCFeedFormatter', - 'add_interwiki_prefix' => $wgRC2UDPInterwikiPrefix, - ); + $performer = $this->getPerformer(); - return $engine->send( $feed, $line ); - } + foreach ( $feeds as $feed ) { + $feed += array( + 'omit_bots' => false, + 'omit_anon' => false, + 'omit_user' => false, + 'omit_minor' => false, + 'omit_patrolled' => false, + ); - /** - * Notify all the feeds about the change. - */ - public function notifyRCFeeds() { - global $wgRCFeeds; + if ( + ( $feed['omit_bots'] && $this->mAttribs['rc_bot'] ) || + ( $feed['omit_anon'] && $performer->isAnon() ) || + ( $feed['omit_user'] && !$performer->isAnon() ) || + ( $feed['omit_minor'] && $this->mAttribs['rc_minor'] ) || + ( $feed['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) || + $this->mAttribs['rc_type'] == RC_EXTERNAL + ) { + continue; + } - foreach ( $wgRCFeeds as $feed ) { $engine = self::getEngine( $feed['uri'] ); if ( isset( $this->mExtra['actionCommentIRC'] ) ) { @@ -331,16 +374,8 @@ class RecentChange { $actionComment = null; } - $omitBots = isset( $feed['omit_bots'] ) ? $feed['omit_bots'] : false; - - if ( - ( $omitBots && $this->mAttribs['rc_bot'] ) || - $this->mAttribs['rc_type'] == RC_EXTERNAL - ) { - continue; - } - - $formatter = new $feed['formatter'](); + /** @var $formatter RCFeedFormatter */ + $formatter = is_object( $feed['formatter'] ) ? $feed['formatter'] : new $feed['formatter'](); $line = $formatter->getLine( $feed, $this, $actionComment ); $engine->send( $feed, $line ); @@ -350,10 +385,11 @@ class RecentChange { /** * Gets the stream engine object for a given URI from $wgRCEngines * - * @param $uri string URI to get the engine object for - * @return object The engine object + * @param string $uri URI to get the engine object for + * @throws MWException + * @return RCFeedEngine The engine object */ - private static function getEngine( $uri ) { + public static function getEngine( $uri ) { global $wgRCEngines; $scheme = parse_url( $uri, PHP_URL_SCHEME ); @@ -369,19 +405,11 @@ class RecentChange { } /** - * @deprecated since 1.22, moved to IRCColourfulRCFeedFormatter - */ - public static function cleanupForIRC( $text ) { - wfDeprecated( __METHOD__, '1.22' ); - return IRCColourfulRCFeedFormatter::cleanupForIRC( $text ); - } - - /** * Mark a given change as patrolled * - * @param $change Mixed: RecentChange or corresponding rc_id - * @param $auto Boolean: for automatic patrol - * @return Array See doMarkPatrolled(), or null if $change is not an existing rc_id + * @param RecentChange|int $change RecentChange or corresponding rc_id + * @param bool $auto For automatic patrol + * @return array See doMarkPatrolled(), or null if $change is not an existing rc_id */ public static function markPatrolled( $change, $auto = false ) { global $wgUser; @@ -393,16 +421,18 @@ class RecentChange { if ( !$change instanceof RecentChange ) { return null; } + return $change->doMarkPatrolled( $wgUser, $auto ); } /** * Mark this RecentChange as patrolled * - * NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and 'markedaspatrollederror-noautopatrol' as errors - * @param $user User object doing the action - * @param $auto Boolean: for automatic patrol - * @return array of permissions errors, see Title::getUserPermissionsErrors() + * NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and + * 'markedaspatrollederror-noautopatrol' as errors + * @param User $user User object doing the action + * @param bool $auto For automatic patrol + * @return array Array of permissions errors, see Title::getUserPermissionsErrors() */ public function doMarkPatrolled( User $user, $auto = false ) { global $wgUseRCPatrol, $wgUseNPPatrol; @@ -420,7 +450,9 @@ class RecentChange { } // Users without the 'autopatrol' right can't patrol their // own revisions - if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) && !$user->isAllowed( 'autopatrol' ) ) { + if ( $user->getName() == $this->getAttribute( 'rc_user_text' ) + && !$user->isAllowed( 'autopatrol' ) + ) { $errors[] = array( 'markedaspatrollederror-noautopatrol' ); } if ( $errors ) { @@ -435,12 +467,13 @@ class RecentChange { // Log this patrol event PatrolLog::record( $this, $auto, $user ); wfRunHooks( 'MarkPatrolledComplete', array( $this->getAttribute( 'rc_id' ), &$user, false ) ); + return array(); } /** * Mark this RecentChange patrolled, without error checking - * @return Integer: number of affected rows + * @return int Number of affected rows */ public function reallyMarkPatrolled() { $dbw = wfGetDB( DB_MASTER ); @@ -457,25 +490,26 @@ class RecentChange { // Invalidate the page cache after the page has been patrolled // to make sure that the Patrol link isn't visible any longer! $this->getTitle()->invalidateCache(); + return $dbw->affectedRows(); } /** * Makes an entry in the database corresponding to an edit * - * @param $timestamp - * @param $title Title - * @param $minor - * @param $user User - * @param $comment - * @param $oldId - * @param $lastTimestamp - * @param $bot - * @param $ip string - * @param $oldSize int - * @param $newSize int - * @param $newId int - * @param $patrol int + * @param string $timestamp + * @param Title $title + * @param bool $minor + * @param User $user + * @param string $comment + * @param int $oldId + * @param string $lastTimestamp + * @param bool $bot + * @param string $ip + * @param int $oldSize + * @param int $newSize + * @param int $newId + * @param int $patrol * @return RecentChange */ public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId, @@ -484,57 +518,57 @@ class RecentChange { $rc->mTitle = $title; $rc->mPerformer = $user; $rc->mAttribs = array( - 'rc_timestamp' => $timestamp, - 'rc_cur_time' => $timestamp, - 'rc_namespace' => $title->getNamespace(), - 'rc_title' => $title->getDBkey(), - 'rc_type' => RC_EDIT, - 'rc_minor' => $minor ? 1 : 0, - 'rc_cur_id' => $title->getArticleID(), - 'rc_user' => $user->getId(), - 'rc_user_text' => $user->getName(), - 'rc_comment' => $comment, + 'rc_timestamp' => $timestamp, + 'rc_namespace' => $title->getNamespace(), + 'rc_title' => $title->getDBkey(), + 'rc_type' => RC_EDIT, + 'rc_source' => self::SRC_EDIT, + 'rc_minor' => $minor ? 1 : 0, + 'rc_cur_id' => $title->getArticleID(), + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_comment' => $comment, 'rc_this_oldid' => $newId, 'rc_last_oldid' => $oldId, - 'rc_bot' => $bot ? 1 : 0, - 'rc_ip' => self::checkIPAddress( $ip ), - 'rc_patrolled' => intval( $patrol ), - 'rc_new' => 0, # obsolete - 'rc_old_len' => $oldSize, - 'rc_new_len' => $newSize, - 'rc_deleted' => 0, - 'rc_logid' => 0, - 'rc_log_type' => null, + 'rc_bot' => $bot ? 1 : 0, + 'rc_ip' => self::checkIPAddress( $ip ), + 'rc_patrolled' => intval( $patrol ), + 'rc_new' => 0, # obsolete + 'rc_old_len' => $oldSize, + 'rc_new_len' => $newSize, + 'rc_deleted' => 0, + 'rc_logid' => 0, + 'rc_log_type' => null, 'rc_log_action' => '', - 'rc_params' => '' + 'rc_params' => '' ); $rc->mExtra = array( 'prefixedDBkey' => $title->getPrefixedDBkey(), 'lastTimestamp' => $lastTimestamp, - 'oldSize' => $oldSize, - 'newSize' => $newSize, - 'pageStatus' => 'changed' + 'oldSize' => $oldSize, + 'newSize' => $newSize, + 'pageStatus' => 'changed' ); $rc->save(); + return $rc; } /** * Makes an entry in the database corresponding to page creation * Note: the title object must be loaded with the new id using resetArticleID() - * @todo Document parameters and return * - * @param $timestamp - * @param $title Title - * @param $minor - * @param $user User - * @param $comment - * @param $bot - * @param $ip string - * @param $size int - * @param $newId int - * @param $patrol int + * @param string $timestamp + * @param Title $title + * @param bool $minor + * @param User $user + * @param string $comment + * @param bool $bot + * @param string $ip + * @param int $size + * @param int $newId + * @param int $patrol * @return RecentChange */ public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot, @@ -543,29 +577,29 @@ class RecentChange { $rc->mTitle = $title; $rc->mPerformer = $user; $rc->mAttribs = array( - 'rc_timestamp' => $timestamp, - 'rc_cur_time' => $timestamp, - 'rc_namespace' => $title->getNamespace(), - 'rc_title' => $title->getDBkey(), - 'rc_type' => RC_NEW, - 'rc_minor' => $minor ? 1 : 0, - 'rc_cur_id' => $title->getArticleID(), - 'rc_user' => $user->getId(), - 'rc_user_text' => $user->getName(), - 'rc_comment' => $comment, - 'rc_this_oldid' => $newId, - 'rc_last_oldid' => 0, - 'rc_bot' => $bot ? 1 : 0, - 'rc_ip' => self::checkIPAddress( $ip ), - 'rc_patrolled' => intval( $patrol ), - 'rc_new' => 1, # obsolete - 'rc_old_len' => 0, - 'rc_new_len' => $size, - 'rc_deleted' => 0, - 'rc_logid' => 0, - 'rc_log_type' => null, - 'rc_log_action' => '', - 'rc_params' => '' + 'rc_timestamp' => $timestamp, + 'rc_namespace' => $title->getNamespace(), + 'rc_title' => $title->getDBkey(), + 'rc_type' => RC_NEW, + 'rc_source' => self::SRC_NEW, + 'rc_minor' => $minor ? 1 : 0, + 'rc_cur_id' => $title->getArticleID(), + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_comment' => $comment, + 'rc_this_oldid' => $newId, + 'rc_last_oldid' => 0, + 'rc_bot' => $bot ? 1 : 0, + 'rc_ip' => self::checkIPAddress( $ip ), + 'rc_patrolled' => intval( $patrol ), + 'rc_new' => 1, # obsolete + 'rc_old_len' => 0, + 'rc_new_len' => $size, + 'rc_deleted' => 0, + 'rc_logid' => 0, + 'rc_log_type' => null, + 'rc_log_action' => '', + 'rc_params' => '' ); $rc->mExtra = array( @@ -576,28 +610,30 @@ class RecentChange { 'pageStatus' => 'created' ); $rc->save(); + return $rc; } /** - * @param $timestamp - * @param $title - * @param $user - * @param $actionComment - * @param $ip string - * @param $type - * @param $action - * @param $target - * @param $logComment - * @param $params - * @param $newId int - * @param $actionCommentIRC string + * @param string $timestamp + * @param Title $title + * @param User $user + * @param string $actionComment + * @param string $ip + * @param string $type + * @param string $action + * @param Title $target + * @param string $logComment + * @param string $params + * @param int $newId + * @param string $actionCommentIRC * @return bool */ public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip, $type, - $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' ) - { + $action, $target, $logComment, $params, $newId = 0, $actionCommentIRC = '' + ) { global $wgLogRestrictions; + # Don't add private logs to RC! if ( isset( $wgLogRestrictions[$type] ) && $wgLogRestrictions[$type] != '*' ) { return false; @@ -605,22 +641,23 @@ class RecentChange { $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, $target, $logComment, $params, $newId, $actionCommentIRC ); $rc->save(); + return true; } /** - * @param $timestamp - * @param $title Title - * @param $user User - * @param $actionComment - * @param $ip string - * @param $type - * @param $action - * @param $target Title - * @param $logComment - * @param $params - * @param $newId int - * @param $actionCommentIRC string + * @param string $timestamp + * @param Title $title + * @param User $user + * @param string $actionComment + * @param string $ip + * @param string $type + * @param string $action + * @param Title $target + * @param string $logComment + * @param string $params + * @param int $newId + * @param string $actionCommentIRC * @return RecentChange */ public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip, @@ -652,45 +689,46 @@ class RecentChange { $rc->mTitle = $target; $rc->mPerformer = $user; $rc->mAttribs = array( - 'rc_timestamp' => $timestamp, - 'rc_cur_time' => $timestamp, - 'rc_namespace' => $target->getNamespace(), - 'rc_title' => $target->getDBkey(), - 'rc_type' => RC_LOG, - 'rc_minor' => 0, - 'rc_cur_id' => $target->getArticleID(), - 'rc_user' => $user->getId(), - 'rc_user_text' => $user->getName(), - 'rc_comment' => $logComment, + 'rc_timestamp' => $timestamp, + 'rc_namespace' => $target->getNamespace(), + 'rc_title' => $target->getDBkey(), + 'rc_type' => RC_LOG, + 'rc_source' => self::SRC_LOG, + 'rc_minor' => 0, + 'rc_cur_id' => $target->getArticleID(), + 'rc_user' => $user->getId(), + 'rc_user_text' => $user->getName(), + 'rc_comment' => $logComment, 'rc_this_oldid' => 0, 'rc_last_oldid' => 0, - 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0, - 'rc_ip' => self::checkIPAddress( $ip ), - 'rc_patrolled' => 1, - 'rc_new' => 0, # obsolete - 'rc_old_len' => null, - 'rc_new_len' => null, - 'rc_deleted' => 0, - 'rc_logid' => $newId, - 'rc_log_type' => $type, + 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot', true ) : 0, + 'rc_ip' => self::checkIPAddress( $ip ), + 'rc_patrolled' => 1, + 'rc_new' => 0, # obsolete + 'rc_old_len' => null, + 'rc_new_len' => null, + 'rc_deleted' => 0, + 'rc_logid' => $newId, + 'rc_log_type' => $type, 'rc_log_action' => $action, - 'rc_params' => $params + 'rc_params' => $params ); $rc->mExtra = array( 'prefixedDBkey' => $title->getPrefixedDBkey(), 'lastTimestamp' => 0, 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage - 'pageStatus' => $pageStatus, + 'pageStatus' => $pageStatus, 'actionCommentIRC' => $actionCommentIRC ); + return $rc; } /** * Initialises the members of this object from a mysql row object * - * @param $row + * @param mixed $row */ public function loadFromRow( $row ) { $this->mAttribs = get_object_vars( $row ); @@ -699,42 +737,6 @@ class RecentChange { } /** - * Makes a pseudo-RC entry from a cur row - * - * @deprected in 1.22 - * @param $row - */ - public function loadFromCurRow( $row ) { - wfDeprecated( __METHOD__, '1.22' ); - $this->mAttribs = array( - 'rc_timestamp' => wfTimestamp( TS_MW, $row->rev_timestamp ), - 'rc_cur_time' => $row->rev_timestamp, - 'rc_user' => $row->rev_user, - 'rc_user_text' => $row->rev_user_text, - 'rc_namespace' => $row->page_namespace, - 'rc_title' => $row->page_title, - 'rc_comment' => $row->rev_comment, - 'rc_minor' => $row->rev_minor_edit ? 1 : 0, - 'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT, - 'rc_cur_id' => $row->page_id, - 'rc_this_oldid' => $row->rev_id, - 'rc_last_oldid' => isset( $row->rc_last_oldid ) ? $row->rc_last_oldid : 0, - 'rc_bot' => 0, - 'rc_ip' => '', - 'rc_id' => $row->rc_id, - 'rc_patrolled' => $row->rc_patrolled, - 'rc_new' => $row->page_is_new, # obsolete - 'rc_old_len' => $row->rc_old_len, - 'rc_new_len' => $row->rc_new_len, - 'rc_params' => isset( $row->rc_params ) ? $row->rc_params : '', - 'rc_log_type' => isset( $row->rc_log_type ) ? $row->rc_log_type : null, - 'rc_log_action' => isset( $row->rc_log_action ) ? $row->rc_log_action : null, - 'rc_logid' => isset( $row->rc_logid ) ? $row->rc_logid : 0, - 'rc_deleted' => $row->rc_deleted // MUST be set - ); - } - - /** * Get an attribute value * * @param string $name Attribute name @@ -754,7 +756,7 @@ class RecentChange { /** * Gets the end part of the diff URL associated with this object * Blank if no diff link should be displayed - * @param $forceCur + * @param bool $forceCur * @return string */ public function diffLinkTrail( $forceCur ) { @@ -769,14 +771,15 @@ class RecentChange { } else { $trail = ''; } + return $trail; } /** * Returns the change size (HTML). * The lengths can be given optionally. - * @param $old int - * @param $new int + * @param int $old + * @param int $new * @return string */ public function getCharacterDifference( $old = 0, $new = 0 ) { @@ -789,6 +792,7 @@ class RecentChange { if ( $old === null || $new === null ) { return ''; } + return ChangesList::showCharacterDifference( $old, $new ); } @@ -803,7 +807,7 @@ class RecentChange { $method = __METHOD__; $dbw = wfGetDB( DB_MASTER ); - $dbw->onTransactionIdle( function() use ( $dbw, $method ) { + $dbw->onTransactionIdle( function () use ( $dbw, $method ) { global $wgRCMaxAge; $cutoff = $dbw->timestamp( time() - $wgRCMaxAge ); @@ -819,7 +823,8 @@ class RecentChange { global $wgRequest; if ( $ip ) { if ( !IP::isIPAddress( $ip ) ) { - throw new MWException( "Attempt to write \"" . $ip . "\" as an IP address into recent changes" ); + throw new MWException( "Attempt to write \"" . $ip . + "\" as an IP address into recent changes" ); } } else { $ip = $wgRequest->getIP(); @@ -827,6 +832,7 @@ class RecentChange { $ip = ''; } } + return $ip; } @@ -835,12 +841,13 @@ class RecentChange { * as the recentchanges table might not be cleared out regularly (so older entries might exist) * or rows which will be deleted soon shouldn't be included. * - * @param $timestamp mixed MWTimestamp compatible timestamp - * @param $tolerance integer Tolerance in seconds + * @param mixed $timestamp MWTimestamp compatible timestamp + * @param int $tolerance Tolerance in seconds * @return bool */ public static function isInRCLifespan( $timestamp, $tolerance = 0 ) { global $wgRCMaxAge; + return wfTimestamp( TS_UNIX, $timestamp ) > time() - $tolerance - $wgRCMaxAge; } } |