deprecated and will be removed in the future.
* The FileBasedSiteLookup class has been deprecated. For a cacheable SiteLookup
implementation, use CachingSiteStore instead.
+* Language::viewPrevNext function is deprecated, use
+ SpecialPage::buildPrevNextNavigation instead
* ManualLogEntry::setTags() is deprecated, use ManualLogEntry::addTags()
instead. The setTags() method was overriding the tags, addTags() doesn't
override, only adds new tags.
* Check if the block prevents a user from resetting their password
*
* @since 1.33
- * @return bool|null The block blocks password reset
+ * @return bool The block blocks password reset
*/
public function appliesToPasswordReset() {
switch ( $this->getSystemBlockType() ) {
case 'wgSoftBlockRanges':
return false;
default:
- return false;
+ return true;
}
}
* @return string
*/
public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
- return self::userToolLinks( $userId, $userText, true, 0, $edits );
+ return self::userToolLinks( $userId, $userText, true, 0, $edits, false );
}
/**
* @return RevisionRecord|null
*/
public function getRevisionByTitle( LinkTarget $linkTarget, $revId = 0, $flags = 0 ) {
+ // TODO should not require Title in future (T206498)
+ $title = Title::newFromLinkTarget( $linkTarget );
$conds = [
- 'page_namespace' => $linkTarget->getNamespace(),
- 'page_title' => $linkTarget->getDBkey()
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
];
if ( $revId ) {
// Use the specified revision ID.
// Since the caller supplied a revision ID, we are pretty sure the revision is
// supposed to exist, so we should try hard to find it.
$conds['rev_id'] = $revId;
- return $this->newRevisionFromConds( $conds, $flags );
+ return $this->newRevisionFromConds( $conds, $flags, $title );
} else {
// Use a join to get the latest revision.
// Note that we don't use newRevisionFromConds here because we don't want to retry
$db = $this->getDBConnectionRefForQueryFlags( $flags );
$conds[] = 'rev_id=page_latest';
- $rev = $this->loadRevisionFromConds( $db, $conds, $flags );
+ $rev = $this->loadRevisionFromConds( $db, $conds, $flags, $title );
return $rev;
}
return ' <span class="history-deleted">' .
$this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
} else {
- return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+ return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle(),
+ // Whether section links should refer to local page (using default false)
+ false,
+ // wikid to generate links for (using default null) */
+ null,
+ // whether parentheses should be rendered as part of the message
+ false );
}
}
}
$classes = array_merge( $classes, $this->getHTMLClasses( $rcObj, $rcObj->watched ) );
- $separator = ' <span class="mw-changeslist-separator">. .</span> ';
+ $separator = ' <span class="mw-changeslist-separator"></span> ';
$data['recentChangesFlags'] = [
'newpage' => $type == RC_NEW,
$isnew ||
$rcObj->mAttribs['rc_type'] == RC_CATEGORIZE
) {
- $links['total-changes'] = $nchanges[$n];
+ $links['total-changes'] = Html::rawElement( 'span', [], $nchanges[$n] );
} else {
- $links['total-changes'] = $this->linkRenderer->makeKnownLink(
- $block0->getTitle(),
- new HtmlArmor( $nchanges[$n] ),
- [ 'class' => 'mw-changeslist-groupdiff' ],
- $queryParams + [
- 'diff' => $currentRevision,
- 'oldid' => $last->mAttribs['rc_last_oldid'],
- ]
+ $links['total-changes'] = Html::rawElement( 'span', [],
+ $this->linkRenderer->makeKnownLink(
+ $block0->getTitle(),
+ new HtmlArmor( $nchanges[$n] ),
+ [ 'class' => 'mw-changeslist-groupdiff' ],
+ $queryParams + [
+ 'diff' => $currentRevision,
+ 'oldid' => $last->mAttribs['rc_last_oldid'],
+ ]
+ )
);
if ( $sinceLast > 0 && $sinceLast < $n ) {
- $links['total-changes-since-last'] = $this->linkRenderer->makeKnownLink(
+ $links['total-changes-since-last'] = Html::rawElement( 'span', [],
+ $this->linkRenderer->makeKnownLink(
$block0->getTitle(),
new HtmlArmor( $sinceLastVisitMsg[$sinceLast] ),
[ 'class' => 'mw-changeslist-groupdiff' ],
'diff' => $currentRevision,
'oldid' => $unvisitedOldid,
]
- );
+ )
+ );
}
}
}
if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
// don't show history link for logs
} elseif ( $namehidden || !$block0->getTitle()->exists() ) {
- $links['history'] = $this->message['enhancedrc-history'];
+ $links['history'] = Html::rawElement( 'span', [], $this->message['enhancedrc-history'] );
} else {
$params = $queryParams;
$params['action'] = 'history';
- $links['history'] = $this->linkRenderer->makeKnownLink(
+ $links['history'] = Html::rawElement( 'span', [],
+ $this->linkRenderer->makeKnownLink(
$block0->getTitle(),
new HtmlArmor( $this->message['enhancedrc-history'] ),
[ 'class' => 'mw-changeslist-history' ],
$params
- );
+ )
+ );
}
# Allow others to alter, remove or add to these links
return '';
}
- $logtext = implode( $this->message['pipe-separator'], $links );
- $logtext = $this->msg( 'parentheses' )->rawParams( $logtext )->escaped();
+ $logtext = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
+ implode( ' ', $links ) );
return ' ' . $logtext;
}
$logPage = new LogPage( $logType );
$logTitle = SpecialPage::getTitleFor( 'Log', $logType );
$logName = $logPage->getName()->text();
- $data['logLink'] = $this->msg( 'parentheses' )
- ->rawParams(
- $this->linkRenderer->makeKnownLink( $logTitle, $logName )
- )->escaped();
+ $data['logLink'] = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
+ $this->linkRenderer->makeKnownLink( $logTitle, $logName )
+ );
} else {
$data['articleLink'] = $this->getArticleLink( $rcObj, $rcObj->unpatrolled, $rcObj->watched );
}
# Diff and hist links
if ( $type != RC_LOG && $type != RC_CATEGORIZE ) {
$query['action'] = 'history';
- $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
+ $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
}
- $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator">. .</span> ';
+ $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator"></span> ';
# Character diff
if ( $this->getConfig()->get( 'RCShowChangedSize' ) ) {
$cd = $this->formatCharacterDifference( $rcObj );
if ( $cd !== '' ) {
$data['characterDiff'] = $cd;
- $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator">. .</span> ';
+ $data['separatorAftercharacterDiff'] = ' <span class="mw-changeslist-separator"></span> ';
}
}
$data['userTalkLink'] = $rcObj->usertalklink;
$data['comment'] = $this->insertComment( $rcObj );
if ( $type == RC_CATEGORIZE ) {
- $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
+ $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query, false );
}
$data['rollback'] = $this->getRollback( $rcObj );
}
] );
// everything else: makes it easier for extensions to add or remove data
- $line .= implode( '', $data );
+ foreach ( $data as $key => $dataItem ) {
+ $line .= Html::rawElement( 'span', [
+ 'class' => 'mw-changeslist-line-inner-' . $key,
+ ], $dataItem );
+ }
$line .= "</td></tr></table>\n";
*
* @param RCCacheEntry $rc
* @param array $query array of key/value pairs to append as a query string
+ * @param bool $useParentheses (optional) Wrap comments in parentheses where needed
* @return string HTML
*/
- public function getDiffHistLinks( RCCacheEntry $rc, array $query ) {
+ public function getDiffHistLinks( RCCacheEntry $rc, array $query, $useParentheses = true ) {
$pageTitle = $rc->getTitle();
if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
// For categorizations we must swap the category title with the page title!
}
}
- $retVal = ' ' . $this->msg( 'parentheses' )
- ->rawParams( $rc->difflink . $this->message['pipe-separator']
- . $this->linkRenderer->makeKnownLink(
- $pageTitle,
- new HtmlArmor( $this->message['hist'] ),
- [ 'class' => 'mw-changeslist-history' ],
- $query
- ) )->escaped();
- return $retVal;
+ $histLink = $this->linkRenderer->makeKnownLink(
+ $pageTitle,
+ new HtmlArmor( $this->message['hist'] ),
+ [ 'class' => 'mw-changeslist-history' ],
+ $query
+ );
+ if ( $useParentheses ) {
+ $retVal = $this->msg( 'parentheses' )
+ ->rawParams( $rc->difflink . $this->message['pipe-separator']
+ . $histLink )->escaped();
+ } else {
+ $retVal = Html::rawElement( 'span', [ 'class' => 'mw-changeslist-links' ],
+ Html::rawElement( 'span', [], $rc->difflink ) .
+ Html::rawElement( 'span', [], $histLink )
+ );
+ }
+ return ' ' . $retVal;
}
/**
if ( !ChangesList::isDeleted( $cacheEntry, Revision::DELETED_USER ) ) {
$cacheEntry->usertalklink = Linker::userToolLinks(
$cacheEntry->mAttribs['rc_user'],
- $cacheEntry->mAttribs['rc_user_text']
+ $cacheEntry->mAttribs['rc_user_text'],
+ // Should the contributions link be red if the user has no edits (using default)
+ false,
+ // Customisation flags (using default 0)
+ 0,
+ // User edit count (using default )
+ null,
+ // do not wrap the message in parentheses
+ false
);
}
* @since 1.21
*/
abstract class JobQueue {
- /** @var string Wiki ID */
+ /** @var string DB domain ID */
protected $domain;
/** @var string Job type */
protected $type;
/**
* @param array $params
- * @throws MWException
+ * @throws JobQueueError
*/
protected function __construct( array $params ) {
$this->domain = $params['domain'] ?? $params['wiki']; // b/c
$this->order = $this->optimalOrder();
}
if ( !in_array( $this->order, $this->supportedOrders() ) ) {
- throw new MWException( __CLASS__ . " does not support '{$this->order}' order." );
+ throw new JobQueueError( __CLASS__ . " does not support '{$this->order}' order." );
}
$this->dupCache = wfGetCache( CACHE_ANYTHING );
$this->aggr = $params['aggregator'] ?? new JobQueueAggregatorNull( [] );
*
* @param array $params
* @return JobQueue
- * @throws MWException
+ * @throws JobQueueError
*/
final public static function factory( array $params ) {
$class = $params['class'];
if ( !class_exists( $class ) ) {
- throw new MWException( "Invalid job queue class '$class'." );
+ throw new JobQueueError( "Invalid job queue class '$class'." );
}
$obj = new $class( $params );
if ( !( $obj instanceof self ) ) {
- throw new MWException( "Class '$class' is not a " . __CLASS__ . " class." );
+ throw new JobQueueError( "Class '$class' is not a " . __CLASS__ . " class." );
}
return $obj;
* @param IJobSpecification[] $jobs
* @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
* @return void
- * @throws MWException
+ * @throws JobQueueError
*/
final public function batchPush( array $jobs, $flags = 0 ) {
$this->assertNotReadOnly();
foreach ( $jobs as $job ) {
if ( $job->getType() !== $this->type ) {
- throw new MWException(
+ throw new JobQueueError(
"Got '{$job->getType()}' job; expected a '{$this->type}' job." );
} elseif ( $job->getReleaseTimestamp() && !$this->supportsDelayedJobs() ) {
- throw new MWException(
+ throw new JobQueueError(
"Got delayed '{$job->getType()}' job; delays are not supported." );
}
}
* This requires $wgJobClasses to be set for the given job type.
* Outside callers should use JobQueueGroup::pop() instead of this function.
*
- * @throws MWException
+ * @throws JobQueueError
* @return Job|bool Returns false if there are no jobs
*/
final public function pop() {
$this->assertNotReadOnly();
if ( !WikiMap::isCurrentWikiDbDomain( $this->domain ) ) {
- throw new MWException(
+ throw new JobQueueError(
"Cannot pop '{$this->type}' job off foreign '{$this->domain}' wiki queue." );
} elseif ( !isset( $wgJobClasses[$this->type] ) ) {
// Do not pop jobs if there is no class for the queue type
- throw new MWException( "Unrecognized job type '{$this->type}'." );
+ throw new JobQueueError( "Unrecognized job type '{$this->type}'." );
}
$job = $this->doPop();
*
* @param Job $job
* @return void
- * @throws MWException
+ * @throws JobQueueError
*/
final public function ack( Job $job ) {
$this->assertNotReadOnly();
if ( $job->getType() !== $this->type ) {
- throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
$this->doAck( $job );
* This does nothing for certain queue classes.
*
* @param IJobSpecification $job
- * @throws MWException
+ * @throws JobQueueError
* @return bool
*/
final public function deduplicateRootJob( IJobSpecification $job ) {
$this->assertNotReadOnly();
if ( $job->getType() !== $this->type ) {
- throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
return $this->doDeduplicateRootJob( $job );
/**
* @see JobQueue::deduplicateRootJob()
* @param IJobSpecification $job
- * @throws MWException
+ * @throws JobQueueError
* @return bool
*/
protected function doDeduplicateRootJob( IJobSpecification $job ) {
if ( !$job->hasRootJobParams() ) {
- throw new MWException( "Cannot register root job; missing parameters." );
+ throw new JobQueueError( "Cannot register root job; missing parameters." );
}
$params = $job->getRootJobParams();
* Check if the "root" job of a given job has been superseded by a newer one
*
* @param Job $job
- * @throws MWException
+ * @throws JobQueueError
* @return bool
*/
final protected function isRootJobOldDuplicate( Job $job ) {
if ( $job->getType() !== $this->type ) {
- throw new MWException( "Got '{$job->getType()}' job; expected '{$this->type}'." );
+ throw new JobQueueError( "Got '{$job->getType()}' job; expected '{$this->type}'." );
}
$isDuplicate = $this->doIsRootJobOldDuplicate( $job );
/**
* @see JobQueue::delete()
- * @throws MWException
+ * @throws JobQueueError
*/
protected function doDelete() {
- throw new MWException( "This method is not implemented." );
+ throw new JobQueueError( "This method is not implemented." );
}
/**
*
* @param array $types List of queues types
* @return array|null (list of non-empty queue types) or null if unsupported
- * @throws MWException
+ * @throws JobQueueError
* @since 1.22
*/
final public function getSiblingQueuesWithJobs( array $types ) {
*
* @param array $types List of queues types
* @return array|null (job type => whether queue is empty) or null if unsupported
- * @throws MWException
+ * @throws JobQueueError
* @since 1.22
*/
final public function getSiblingQueueSizes( array $types ) {
/**
* Get the list of job types that have non-empty queues
*
- * @return array List of job types that have non-empty queues
+ * @return string[] List of job types that have non-empty queues
*/
public function getQueuesWithJobs() {
$types = [];
foreach ( $this->getCoalescedQueues() as $info ) {
- $nonEmpty = $info['queue']->getSiblingQueuesWithJobs( $this->getQueueTypes() );
+ /** @var JobQueue $queue */
+ $queue = $info['queue'];
+ $nonEmpty = $queue->getSiblingQueuesWithJobs( $this->getQueueTypes() );
if ( is_array( $nonEmpty ) ) { // batching features supported
$types = array_merge( $types, $nonEmpty );
} else { // we have to go through the queues in the bucket one-by-one
/**
* Get the size of the queus for a list of job types
*
- * @return array Map of (job type => size)
+ * @return int[] Map of (job type => size)
*/
public function getQueueSizes() {
$sizeMap = [];
foreach ( $this->getCoalescedQueues() as $info ) {
- $sizes = $info['queue']->getSiblingQueueSizes( $this->getQueueTypes() );
+ /** @var JobQueue $queue */
+ $queue = $info['queue'];
+ $sizes = $queue->getSiblingQueueSizes( $this->getQueueTypes() );
if ( is_array( $sizes ) ) { // batching features supported
$sizeMap = $sizeMap + $sizes;
} else { // we have to go through the queues in the bucket one-by-one
}
/**
- * @return array
+ * @return JobQueue[]
*/
protected function getCoalescedQueues() {
global $wgJobTypeConf;
$s .= $loglist->beginLogEventsList() .
$logBody .
$loglist->endLogEventsList();
+ // add styles for change tags
+ $context->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
} elseif ( $showIfEmpty ) {
$s = Html::rawElement( 'div', [ 'class' => 'mw-warning-logempty' ],
$context->msg( 'logempty' )->parse() );
$user->getName(),
true, // redContribsWhenNoEdits
$toolFlags,
- $user->getEditCount()
+ $user->getEditCount(),
+ // do not render parenthesises in the HTML markup (CSS will provide)
+ false
);
}
}
$ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
Hooks::run( 'ParserGetVariableValueTs', [ &$parser, &$ts ] );
+ // In miser mode, disable words that always cause double-parses on page save (T137900)
+ static $slowRevWords = [ 'revisionid' => true ]; // @TODO: 'revisiontimestamp'
+ if (
+ isset( $slowRevWords[$index] ) &&
+ $this->siteConfig->get( 'MiserMode' ) &&
+ !$this->mOptions->getInterfaceMessage() &&
+ // @TODO: disallow this word on all namespaces
+ MWNamespace::isContent( $this->mTitle->getNamespace() )
+ ) {
+ return $this->mRevisionId ? '-' : '';
+ };
+
$pageLang = $this->getFunctionLang();
switch ( $index ) {
$miserMaxResults = $this->getConfig()->get( 'MiserMode' )
&& ( $this->offset + $this->limit >= $this->getMaxResults() );
$atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
- $paging = $this->getLanguage()->viewPrevNext( $this->getPageTitle( $par ), $this->offset,
- $this->limit, $this->linkParameters(), $atEnd );
+ $paging = $this->buildPrevNextNavigation( $this->offset,
+ $this->limit, $this->linkParameters(), $atEnd, $par );
$out->addHTML( '<p>' . $paging . '</p>' );
} else {
# No results to show, so don't bother with "showing X of Y" etc.
public function setLinkRenderer( LinkRenderer $linkRenderer ) {
$this->linkRenderer = $linkRenderer;
}
+
+ /**
+ * Generate (prev x| next x) (20|50|100...) type links for paging
+ *
+ * @param int $offset
+ * @param int $limit
+ * @param array $query Optional URL query parameter string
+ * @param bool $atend Optional param for specified if this is the last page
+ * @param string|bool $subpage Optional param for specifying subpage
+ * @return string
+ */
+ protected function buildPrevNextNavigation( $offset, $limit,
+ array $query = [], $atend = false, $subpage = false
+ ) {
+ $lang = $this->getLanguage();
+
+ # Make 'previous' link
+ $prev = $this->msg( 'prevn' )->numParams( $limit )->text();
+ if ( $offset > 0 ) {
+ $plink = $this->numLink( max( $offset - $limit, 0 ), $limit, $query,
+ $prev, 'prevn-title', 'mw-prevlink', $subpage );
+ } else {
+ $plink = htmlspecialchars( $prev );
+ }
+
+ # Make 'next' link
+ $next = $this->msg( 'nextn' )->numParams( $limit )->text();
+ if ( $atend ) {
+ $nlink = htmlspecialchars( $next );
+ } else {
+ $nlink = $this->numLink( $offset + $limit, $limit,
+ $query, $next, 'nextn-title', 'mw-nextlink', $subpage );
+ }
+
+ # Make links to set number of items per page
+ $numLinks = [];
+ foreach ( [ 20, 50, 100, 250, 500 ] as $num ) {
+ $numLinks[] = $this->numLink( $offset, $num, $query,
+ $lang->formatNum( $num ), 'shown-title', 'mw-numlink', $subpage );
+ }
+
+ return $this->msg( 'viewprevnext' )->rawParams( $plink, $nlink, $lang->pipeList( $numLinks ) )->
+ escaped();
+ }
+
+ /**
+ * Helper function for buildPrevNextNavigation() that generates links
+ *
+ * @param int $offset
+ * @param int $limit
+ * @param array $query Extra query parameters
+ * @param string $link Text to use for the link; will be escaped
+ * @param string $tooltipMsg Name of the message to use as tooltip
+ * @param string $class Value of the "class" attribute of the link
+ * @param string|bool $subpage Optional param for specifying subpage
+ * @return string HTML fragment
+ */
+ private function numLink( $offset, $limit, array $query, $link,
+ $tooltipMsg, $class, $subpage = false
+ ) {
+ $query = [ 'limit' => $limit, 'offset' => $offset ] + $query;
+ $tooltip = $this->msg( $tooltipMsg )->numParams( $limit )->text();
+ $href = $this->getPageTitle( $subpage )->getLocalURL( $query );
+ return Html::element( 'a', [ 'href' => $href,
+ 'title' => $tooltip, 'class' => $class ], $link );
+ }
}
$offset = $this->offset;
}
- $prevnext = $this->getLanguage()->viewPrevNext(
- $this->getPageTitle(),
+ $prevnext = $this->buildPrevNextNavigation(
$offset,
$this->limit,
$this->powerSearchOptions() + [ 'search' => $term ],
<td class="mw-changeslist-line-inner">
{{# rev-deleted-event }}<span class="history-deleted">{{{ . }}}</span>{{/ rev-deleted-event }}
{{{ articleLink }}}{{{ languageDirMark }}}{{{ logText }}}
- <span class="mw-changeslist-separator">. .</span>
- {{# charDifference }}{{{ . }}} <span class="mw-changeslist-separator">. .</span>{{/ charDifference }}
+ <span class="mw-changeslist-separator"></span>
+ {{# charDifference }}{{{ . }}} <span class="mw-changeslist-separator"></span>{{/ charDifference }}
<span class="changedby">{{{ users }}}</span>
{{ numberofWatchingusers }}
</td>
* @param array $query Optional URL query parameter string
* @param bool $atend Optional param for specified if this is the last page
* @return string
+ * @deprecated since 1.33, use SpecialPage::viewPrevNext()
+ * instead.
*/
public function viewPrevNext( Title $title, $offset, $limit,
array $query = [], $atend = false
*/
protected function pageableSortedRows( ResultWrapper $res, $column, $limit ) {
$rows = iterator_to_array( $res, false );
- $count = count( $rows );
- if ( !$count ) {
- return [ [], null ]; // nothing to do
- } elseif ( $count < $limit ) {
- return [ $rows, $rows[$count - 1]->$column ]; // no more rows left
+
+ // Nothing to do
+ if ( !$rows ) {
+ return [ [], null ];
+ }
+
+ $lastValue = end( $rows )->$column;
+ if ( count( $rows ) < $limit ) {
+ return [ $rows, $lastValue ];
}
- $lastValue = $rows[$count - 1]->$column; // should be the highest
- for ( $i = $count - 1; $i >= 0; --$i ) {
- if ( $rows[$i]->$column === $lastValue ) {
- unset( $rows[$i] );
- } else {
+
+ for ( $i = count( $rows ) - 1; $i >= 0; --$i ) {
+ if ( $rows[$i]->$column !== $lastValue ) {
break;
}
+
+ unset( $rows[$i] );
+ }
+
+ // No more rows left
+ if ( !$rows ) {
+ return [ [], null ];
}
- $lastValueLeft = count( $rows ) ? $rows[count( $rows ) - 1]->$column : null;
- return [ $rows, $lastValueLeft ];
+ return [ $rows, end( $rows )->$column ];
}
}
protected function assertMoved( $from, $to, $id, $opts = null ) {
$opts = (array)$opts;
+ Title::clearCaches();
$fromTitle = Title::newFromText( $from );
$toTitle = Title::newFromText( $to );
$html = $this->createCategorizationLine(
$this->getCategorizationChange( '20150629191735', 0, 0 )
);
- $this->assertNotContains( '(diff | hist)', strip_tags( $html ) );
+ $this->assertNotContains( 'diffhist', strip_tags( $html ) );
}
public function testCategorizationLineFormattingWithRevision() {
$html = $this->createCategorizationLine(
$this->getCategorizationChange( '20150629191735', 1025, 1024 )
);
- $this->assertContains( '(diff | hist)', strip_tags( $html ) );
+ $this->assertContains( 'diffhist', strip_tags( $html ) );
}
/**
$this->assertValidHTML( $cacheEntry->usertalklink );
$this->assertRegExp(
- '#^ <span class="mw-usertoollinks">\(.*<a .+>talk</a>.*\)</span>#',
+ '#^ <span class="mw-usertoollinks mw-changeslist-links">.*<span><a .+>talk</a></span>.*</span>#',
$cacheEntry->usertalklink,
'verify user talk link'
);
$this->assertValidHTML( $cacheEntry->usertalklink );
$this->assertRegExp(
- '#^ <span class="mw-usertoollinks">\(.*<a .+>contribs</a>.*\)</span>$#',
+ '#^ <span class="mw-usertoollinks mw-changeslist-links">.*<span><a .+>' .
+ 'contribs</a></span>.*</span>$#',
$cacheEntry->usertalklink,
'verify user tool links'
);
<?php
+use Wikimedia\TestingAccessWrapper;
+
/**
* @covers SpecialPage
*
$this->assertTrue( true );
}
+ public function provideBuildPrevNextNavigation() {
+ yield [ 0, 20, false, false ];
+ yield [ 17, 20, false, false ];
+ yield [ 0, 17, false, false ];
+ yield [ 0, 20, true, 'Foo' ];
+ yield [ 17, 20, true, 'Föö_Bär' ];
+ }
+
+ /**
+ * @dataProvider provideBuildPrevNextNavigation
+ */
+ public function testBuildPrevNextNavigation( $offset, $limit, $atEnd, $subPage ) {
+ $this->setUserLang( Language::factory( 'qqx' ) ); // disable i18n
+
+ $specialPage = new SpecialPage( 'Watchlist' );
+ $specialPage = TestingAccessWrapper::newFromObject( $specialPage );
+
+ $html = $specialPage->buildPrevNextNavigation(
+ $offset,
+ $limit,
+ [ 'x' => 25 ],
+ $atEnd,
+ $subPage
+ );
+
+ $this->assertStringStartsWith( '(viewprevnext:', $html );
+
+ preg_match_all( '!<a.*?</a>!', $html, $m, PREG_PATTERN_ORDER );
+ $links = $m[0];
+
+ foreach ( $links as $a ) {
+ if ( $subPage ) {
+ $this->assertContains( 'Special:Watchlist/' . wfUrlencode( $subPage ), $a );
+ } else {
+ $this->assertContains( 'Special:Watchlist', $a );
+ $this->assertNotContains( 'Special:Watchlist/', $a );
+ }
+ $this->assertContains( 'x=25', $a );
+ }
+
+ $i = 0;
+
+ if ( $offset > 0 ) {
+ $this->assertContains(
+ 'limit=' . $limit . '&offset=' . max( 0, $offset - $limit ) . '&',
+ $links[ $i ]
+ );
+ $this->assertContains( 'title="(prevn-title: ' . $limit . ')"', $links[$i] );
+ $this->assertContains( 'class="mw-prevlink"', $links[$i] );
+ $this->assertContains( '>(prevn: ' . $limit . ')<', $links[$i] );
+ $i += 1;
+ }
+
+ if ( !$atEnd ) {
+ $this->assertContains(
+ 'limit=' . $limit . '&offset=' . ( $offset + $limit ) . '&',
+ $links[ $i ]
+ );
+ $this->assertContains( 'title="(nextn-title: ' . $limit . ')"', $links[$i] );
+ $this->assertContains( 'class="mw-nextlink"', $links[$i] );
+ $this->assertContains( '>(nextn: ' . $limit . ')<', $links[$i] );
+ $i += 1;
+ }
+
+ $this->assertCount( 5 + $i, $links );
+
+ $this->assertContains( 'limit=20&offset=' . $offset, $links[$i] );
+ $this->assertContains( 'title="(shown-title: 20)"', $links[$i] );
+ $this->assertContains( 'class="mw-numlink"', $links[$i] );
+ $this->assertContains( '>20<', $links[$i] );
+ $i += 4;
+
+ $this->assertContains( 'limit=500&offset=' . $offset, $links[$i] );
+ $this->assertContains( 'title="(shown-title: 500)"', $links[$i] );
+ $this->assertContains( 'class="mw-numlink"', $links[$i] );
+ $this->assertContains( '>500<', $links[$i] );
+ }
+
}
'globalBlock' => null,
'isAllowed' => true,
],
+ 'blocked with an unknown system block type' => [
+ 'passwordResetRoutes' => [ 'username' => true ],
+ 'enableEmail' => true,
+ 'allowsAuthenticationDataChange' => true,
+ 'canEditPrivate' => true,
+ 'block' => new Block( [ 'systemBlock' => 'unknown' ] ),
+ 'globalBlock' => null,
+ 'isAllowed' => false,
+ ],
'all OK' => [
'passwordResetRoutes' => [ 'username' => true ],
'enableEmail' => true,