deprecated in 1.24) were removed.
* wfMemcKey() and wfGlobalCacheKey() were deprecated. ObjectCache::makeKey() and
ObjectCache::makeGlobalKey() should be used instead.
+* (T146304) Preprocessor handling of LanguageConverter markup has been improved.
+ As a result of the new uniform handling, '-{' may need to be escaped
+ (for example, as '-<nowiki/>{') where it occurs inside template arguments
+ or wikilinks.
== Compatibility ==
MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for
private $mTarget = null;
/**
- * @var bool Whether parser output should contain table of contents
+ * @var bool Whether parser output contains a table of contents
*/
- private $mEnableTOC = true;
+ private $mEnableTOC = false;
/**
* @var bool Whether parser output should contain section edit links
$outputPage = $this;
Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
Hooks::run( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
+
+ // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
+ // so that extensions may modify ParserOutput to toggle TOC.
+ // This cannot be moved to addParserOutputText because that is not
+ // called by EditPage for Preview.
+ if ( $parserOutput->getTOCEnabled() && $parserOutput->getTOCHTML() ) {
+ $this->mEnableTOC = true;
+ }
}
/**
*/
function addParserOutput( $parserOutput ) {
$this->addParserOutputMetadata( $parserOutput );
- $parserOutput->setTOCEnabled( $this->mEnableTOC );
// Touch section edit links only if not previously disabled
if ( $parserOutput->getEditSectionTokens() ) {
}
$sk = $this->getSkin();
- // add skin specific modules
- $modules = $sk->getDefaultModules();
-
- // Enforce various default modules for all pages and all skins
- $coreModules = [
- // Keep this list as small as possible
- 'site',
- 'mediawiki.page.startup',
- 'mediawiki.user',
- ];
-
- // Support for high-density display images if enabled
- if ( $config->get( 'ResponsiveImages' ) ) {
- $coreModules[] = 'mediawiki.hidpi';
- }
-
- $this->addModules( $coreModules );
- foreach ( $modules as $group ) {
+ foreach ( $sk->getDefaultModules() as $group ) {
$this->addModules( $group );
}
+
MWDebug::addModules( $this );
// Avoid PHP 7.1 warning of passing $this by reference
}
/**
- * Enables/disables TOC, doesn't override __NOTOC__
- * @param bool $flag
- * @since 1.22
- */
- public function enableTOC( $flag = true ) {
- $this->mEnableTOC = $flag;
- }
-
- /**
+ * Whether the output has a table of contents
* @return bool
* @since 1.22
*/
* @file
*/
+use MediaWiki\MediaWikiServices;
+
/**
* @ingroup API
*/
$result_array['title'] = $titleObj->getPrefixedText();
$result_array['pageid'] = $pageid ?: $pageObj->getId();
+ if ( $params['disabletoc'] ) {
+ $p_result->setTOCEnabled( false );
+ }
+
+ if ( isset( $params['useskin'] ) ) {
+ $factory = MediaWikiServices::getInstance()->getSkinFactory();
+ $skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
+ } else {
+ $skin = null;
+ }
+
+ $outputPage = null;
+ if ( $skin || isset( $prop['headhtml'] ) ) {
+ // Enabling the skin via 'useskin' or 'headhtml' gets OutputPage and
+ // Skin involved, which (among others) applies these hooks:
+ // - ParserOutputHooks
+ // - Hook: LanguageLinks
+ // - Hook: OutputPageParserOutput
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $titleObj );
+ $context->setWikiPage( $pageObj );
+
+ if ( $skin ) {
+ // Use the skin specified by 'useskin'
+ $context->setSkin( $skin );
+ // Context clones the skin, refetch to stay in sync. (T166022)
+ $skin = $context->getSkin();
+ }
+
+ $outputPage = new OutputPage( $context );
+ $outputPage->addParserOutputMetadata( $p_result );
+ $context->setOutput( $outputPage );
+
+ if ( $skin ) {
+ // Based on OutputPage::output()
+ foreach ( $skin->getDefaultModules() as $group ) {
+ $outputPage->addModules( $group );
+ }
+ }
+ }
+
if ( !is_null( $oldid ) ) {
$result_array['revid'] = intval( $oldid );
}
$result_array['redirects'] = $redirValues;
}
- if ( $params['disabletoc'] ) {
- $p_result->setTOCEnabled( false );
- }
-
if ( isset( $prop['text'] ) ) {
$result_array['text'] = $p_result->getText();
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
}
if ( isset( $prop['langlinks'] ) ) {
- $langlinks = $p_result->getLanguageLinks();
-
- if ( $params['effectivelanglinks'] ) {
- // Link flags are ignored for now, but may in the future be
- // included in the result.
- $linkFlags = [];
- Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
+ if ( $skin ) {
+ $langlinks = $outputPage->getLanguageLinks();
+ } else {
+ $langlinks = $p_result->getLanguageLinks();
+ // The deprecated 'effectivelanglinks' option depredates OutputPage
+ // support via 'useskin'. If not already applied, then run just this
+ // one hook of OutputPage::addParserOutputMetadata here.
+ if ( $params['effectivelanglinks'] ) {
+ $linkFlags = [];
+ Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
+ }
}
- } else {
- $langlinks = false;
- }
- if ( isset( $prop['langlinks'] ) ) {
$result_array['langlinks'] = $this->formatLangLinks( $langlinks );
}
if ( isset( $prop['categories'] ) ) {
}
if ( isset( $prop['headitems'] ) ) {
- $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
+ if ( $skin ) {
+ $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
+ } else {
+ $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
+ }
$this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
}
if ( isset( $prop['headhtml'] ) ) {
- $context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $titleObj );
- $context->setWikiPage( $pageObj );
-
- // We need an OutputPage tied to $context, not to the
- // RequestContext at the root of the stack.
- $output = new OutputPage( $context );
- $output->addParserOutputMetadata( $p_result );
-
- $result_array['headhtml'] = $output->headElement( $context->getSkin() );
+ $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
}
if ( isset( $prop['modules'] ) ) {
- $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
- $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
- $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
+ if ( $skin ) {
+ $result_array['modules'] = $outputPage->getModules();
+ $result_array['modulescripts'] = $outputPage->getModuleScripts();
+ $result_array['modulestyles'] = $outputPage->getModuleStyles();
+ } else {
+ $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
+ $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
+ $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
+ }
}
if ( isset( $prop['jsconfigvars'] ) ) {
- $result_array['jsconfigvars'] =
- ApiResult::addMetadataToResultVars( $p_result->getJsConfigVars() );
+ $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
+ $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
}
if ( isset( $prop['encodedjsconfigvars'] ) ) {
+ $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
$result_array['encodedjsconfigvars'] = FormatJson::encode(
- $p_result->getJsConfigVars(), false, FormatJson::ALL_OK
+ $jsconfigvars,
+ false,
+ FormatJson::ALL_OK
);
$result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
}
}
if ( isset( $prop['indicators'] ) ) {
- $result_array['indicators'] = (array)$p_result->getIndicators();
+ if ( $skin ) {
+ $result_array['indicators'] = (array)$outputPage->getIndicators();
+ } else {
+ $result_array['indicators'] = (array)$p_result->getIndicators();
+ }
ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
}
'wrapoutputclass' => 'mw-parser-output',
'pst' => false,
'onlypst' => false,
- 'effectivelanglinks' => false,
+ 'effectivelanglinks' => [
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
'section' => null,
'sectiontitle' => [
ApiBase::PARAM_TYPE => 'string',
'preview' => false,
'sectionpreview' => false,
'disabletoc' => false,
+ 'useskin' => [
+ ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ),
+ ],
'contentformat' => [
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
],
"apihelp-parse-param-preview": "Parse in preview mode.",
"apihelp-parse-param-sectionpreview": "Parse in section preview mode (enables preview mode too).",
"apihelp-parse-param-disabletoc": "Omit table of contents in output.",
+ "apihelp-parse-param-useskin": "Apply the selected skin to the parser output. May affect the following properties: <kbd>langlinks</kbd>, <kbd>headitems</kbd>, <kbd>modules</kbd>, <kbd>jsconfigvars</kbd>, <kbd>indicators</kbd>.",
"apihelp-parse-param-contentformat": "Content serialization format used for the input text. Only valid when used with $1text.",
"apihelp-parse-param-contentmodel": "Content model of the input text. If omitted, $1title must be specified, and default will be the model of the specified title. Only valid when used with $1text.",
"apihelp-parse-example-page": "Parse a page.",
"apihelp-parse-param-generatexml": "{{doc-apihelp-param|parse|generatexml|params=* $1 - Value of the constant CONTENT_MODEL_WIKITEXT|paramstart=2}}",
"apihelp-parse-param-preview": "{{doc-apihelp-param|parse|preview}}",
"apihelp-parse-param-sectionpreview": "{{doc-apihelp-param|parse|sectionpreview}}",
+ "apihelp-parse-param-useskin": "{{doc-apihelp-param|parse|useskin}}",
"apihelp-parse-param-disabletoc": "{{doc-apihelp-param|parse|disabletoc}}",
"apihelp-parse-param-contentformat": "{{doc-apihelp-param|parse|contentformat}}",
"apihelp-parse-param-contentmodel": "{{doc-apihelp-param|parse|contentmodel}}",
$toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
$this->mOutput->setTOCHTML( $toc );
$toc = self::TOC_START . $toc . self::TOC_END;
- $this->mOutput->addModules( 'mediawiki.toc' );
}
if ( $isMain ) {
],
'-{' => [
'end' => '}-',
- 'names' => [ 1 => null ],
- 'min' => 1,
- 'max' => 1,
+ 'names' => [ 2 => null ],
+ 'min' => 2,
+ 'max' => 2,
],
];
$searchBase = "[{<\n"; # }
if ( !$wgDisableLangConversion ) {
- // FIXME: disabled due to T153761
- // $searchBase .= '-';
+ $searchBase .= '-';
}
// For fast reverse searches
$search = $searchBase;
if ( $stack->top === false ) {
$currentClosing = '';
+ } elseif (
+ $stack->top->close === '}-' &&
+ $stack->top->count > 2
+ ) {
+ # adjust closing for -{{{...{{
+ $currentClosing = '}';
+ $search .= $currentClosing;
} else {
$currentClosing = $stack->top->close;
$search .= $currentClosing;
} elseif ( isset( $this->rules[$curChar] ) ) {
$found = 'open';
$rule = $this->rules[$curChar];
- } elseif ( $curChar == '-' ) {
- $found = 'dash';
} else {
- # Some versions of PHP have a strcspn which stops on null characters
- # Ignore and continue
+ # Some versions of PHP have a strcspn which stops on
+ # null characters; ignore these and continue.
+ # We also may get '-' and '}' characters here which
+ # don't match -{ or $currentClosing. Add these to
+ # output and continue.
+ if ( $curChar == '-' || $curChar == '}' ) {
+ $accum .= $curChar;
+ }
++$i;
continue;
}
} elseif ( $found == 'open' ) {
# count opening brace characters
$curLen = strlen( $curChar );
- $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i );
+ $count = ( $curLen > 1 ) ?
+ # allow the final character to repeat
+ strspn( $text, $curChar[$curLen-1], $i+1 ) + 1 :
+ strspn( $text, $curChar, $i );
# we need to add to stack only if opening brace count is enough for one of the rules
if ( $count >= $rule['min'] ) {
# Add literal brace(s)
$accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
}
- $i += $curLen * $count;
+ $i += $count;
} elseif ( $found == 'close' ) {
$piece = $stack->top;
# lets check if there are enough characters for closing brace
$maxCount = $piece->count;
+ if ( $piece->close === '}-' && $curChar === '}' ) {
+ $maxCount--; # don't try to match closing '-' as a '}'
+ }
$curLen = strlen( $curChar );
- $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i, $maxCount );
+ $count = ( $curLen > 1 ) ? $curLen :
+ strspn( $text, $curChar, $i, $maxCount );
# check for maximum matching characters (if there are 5 closing
# characters, we will probably need only 3 - depending on the rules)
$rule = $this->rules[$piece->open];
+ if ( $piece->close === '}-' && $piece->count > 2 ) {
+ # tweak for -{..{{ }}..}-
+ $rule = $this->rules['{'];
+ }
if ( $count > $rule['max'] ) {
# The specified maximum exists in the callback array, unless the caller
# has made an error
if ( $matchingCount <= 0 ) {
# No matching element found in callback array
# Output a literal closing brace and continue
- $accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
- $i += $curLen * $count;
+ $endText = substr( $text, $i, $count );
+ $accum .= htmlspecialchars( $endText );
+ $i += $count;
continue;
}
$name = $rule['names'][$matchingCount];
if ( $name === null ) {
// No element, just literal text
- $element = $piece->breakSyntax( $matchingCount ) . str_repeat( $rule['end'], $matchingCount );
+ $endText = substr( $text, $i, $matchingCount );
+ $element = $piece->breakSyntax( $matchingCount ) . $endText;
} else {
# Create XML element
# Note: $parts is already XML, does not need to be encoded further
}
# Advance input pointer
- $i += $curLen * $matchingCount;
+ $i += $matchingCount;
# Unwind the stack
$stack->pop();
$stack->push( $piece );
$accum =& $stack->getAccum();
} else {
- $accum .= str_repeat( $piece->open, $piece->count );
+ $s = substr( $piece->open, 0, -1 );
+ $s .= str_repeat(
+ substr( $piece->open, -1 ),
+ $piece->count - strlen( $s )
+ );
+ $accum .= $s;
}
}
$flags = $stack->getFlags();
if ( $openingCount === false ) {
$openingCount = $this->count;
}
- $s = str_repeat( $this->open, $openingCount );
+ $s = substr( $this->open, 0, -1 );
+ $s .= str_repeat(
+ substr( $this->open, -1 ),
+ $openingCount - strlen( $s )
+ );
$first = true;
foreach ( $this->parts as $part ) {
if ( $first ) {
$searchBase = "[{<\n";
if ( !$wgDisableLangConversion ) {
- // FIXME: disabled due to T153761
- // $searchBase .= '-';
+ $searchBase .= '-';
}
// For fast reverse searches
$search = $searchBase;
if ( $stack->top === false ) {
$currentClosing = '';
+ } elseif (
+ $stack->top->close === '}-' &&
+ $stack->top->count > 2
+ ) {
+ # adjust closing for -{{{...{{
+ $currentClosing = '}';
+ $search .= $currentClosing;
} else {
$currentClosing = $stack->top->close;
$search .= $currentClosing;
} elseif ( isset( $this->rules[$curChar] ) ) {
$found = 'open';
$rule = $this->rules[$curChar];
- } elseif ( $curChar == '-' ) {
- $found = 'dash';
} else {
- # Some versions of PHP have a strcspn which stops on null characters
- # Ignore and continue
+ # Some versions of PHP have a strcspn which stops on
+ # null characters; ignore these and continue.
+ # We also may get '-' and '}' characters here which
+ # don't match -{ or $currentClosing. Add these to
+ # output and continue.
+ if ( $curChar == '-' || $curChar == '}' ) {
+ self::addLiteral( $accum, $curChar );
+ }
++$i;
continue;
}
} elseif ( $found == 'open' ) {
# count opening brace characters
$curLen = strlen( $curChar );
- $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i );
+ $count = ( $curLen > 1 ) ?
+ # allow the final character to repeat
+ strspn( $text, $curChar[$curLen-1], $i+1 ) + 1 :
+ strspn( $text, $curChar, $i );
# we need to add to stack only if opening brace count is enough for one of the rules
if ( $count >= $rule['min'] ) {
# Add literal brace(s)
self::addLiteral( $accum, str_repeat( $curChar, $count ) );
}
- $i += $curLen * $count;
+ $i += $count;
} elseif ( $found == 'close' ) {
$piece = $stack->top;
# lets check if there are enough characters for closing brace
$maxCount = $piece->count;
+ if ( $piece->close === '}-' && $curChar === '}' ) {
+ $maxCount--; # don't try to match closing '-' as a '}'
+ }
$curLen = strlen( $curChar );
- $count = ( $curLen > 1 ) ? 1 : strspn( $text, $curChar, $i, $maxCount );
+ $count = ( $curLen > 1 ) ? $curLen :
+ strspn( $text, $curChar, $i, $maxCount );
# check for maximum matching characters (if there are 5 closing
# characters, we will probably need only 3 - depending on the rules)
$rule = $this->rules[$piece->open];
+ if ( $piece->close === '}-' && $piece->count > 2 ) {
+ # tweak for -{..{{ }}..}-
+ $rule = $this->rules['{'];
+ }
if ( $count > $rule['max'] ) {
# The specified maximum exists in the callback array, unless the caller
# has made an error
if ( $matchingCount <= 0 ) {
# No matching element found in callback array
# Output a literal closing brace and continue
- self::addLiteral( $accum, str_repeat( $curChar, $count ) );
- $i += $curLen * $count;
+ $endText = substr( $text, $i, $count );
+ self::addLiteral( $accum, $endText );
+ $i += $count;
continue;
}
$name = $rule['names'][$matchingCount];
if ( $name === null ) {
// No element, just literal text
+ $endText = substr( $text, $i, $matchingCount );
$element = $piece->breakSyntax( $matchingCount );
- self::addLiteral( $element, str_repeat( $rule['end'], $matchingCount ) );
+ self::addLiteral( $element, $endText );
} else {
# Create XML element
$parts = $piece->parts;
}
# Advance input pointer
- $i += $curLen * $matchingCount;
+ $i += $matchingCount;
# Unwind the stack
$stack->pop();
$stack->push( $piece );
$accum =& $stack->getAccum();
} else {
- self::addLiteral( $accum, str_repeat( $piece->open, $piece->count ) );
+ $s = substr( $piece->open, 0, -1 );
+ $s .= str_repeat(
+ substr( $piece->open, -1 ),
+ $piece->count - strlen( $s )
+ );
+ self::addLiteral( $accum, $s );
}
}
if ( $openingCount === false ) {
$openingCount = $this->count;
}
- $accum = [ str_repeat( $this->open, $openingCount ) ];
+ $s = substr( $this->open, 0, -1 );
+ $s .= str_repeat(
+ substr( $this->open, -1 ),
+ $openingCount - strlen( $s )
+ );
+ $accum = [ $s ];
$lastIndex = 0;
$first = true;
foreach ( $this->parts as $part ) {
global $wgUseAjax, $wgEnableAPI, $wgEnableWriteAPI;
$out = $this->getOutput();
+ $config = $this->getConfig();
$user = $out->getUser();
$modules = [
+ // modules not specific to any specific skin or page
+ 'core' => [
+ // Enforce various default modules for all pages and all skins
+ // Keep this list as small as possible
+ 'site',
+ 'mediawiki.page.startup',
+ 'mediawiki.user',
+ ],
// modules that enhance the page content in some way
'content' => [
'mediawiki.page.ready',
'user' => [],
];
+ // Support for high-density display images if enabled
+ if ( $config->get( 'ResponsiveImages' ) ) {
+ $modules['core'][] = 'mediawiki.hidpi';
+ }
+
// Preload jquery.tablesorter for mediawiki.page.ready
if ( strpos( $out->getHTML(), 'sortable' ) !== false ) {
$modules['content'][] = 'jquery.tablesorter';
$modules['content'][] = 'jquery.makeCollapsible';
}
+ if ( $out->isTOCEnabled() ) {
+ $modules['content'][] = 'mediawiki.toc';
+ }
+
// Add various resources if required
if ( $wgUseAjax && $wgEnableAPI ) {
if ( $wgEnableWriteAPI && $user->isLoggedIn()
$this->outputHeader();
$out = $this->getOutput();
+ $out->addModules( 'mediawiki.special.newFiles' );
$this->addHelpLink( 'Help:New images' );
$opts = new FormOptions();
$opts->add( 'hidepatrolled', false );
$opts->add( 'limit', 50 );
$opts->add( 'offset', '' );
+ $opts->add( 'start', '' );
+ $opts->add( 'end', '' );
$opts->fetchValuesFromRequest( $this->getRequest() );
$opts->setValue( is_numeric( $par ) ? 'limit' : 'like', $par );
}
+ // If start date comes after end date chronologically, swap them.
+ // They are swapped in the interface by JS.
+ $start = $opts->getValue( 'start' );
+ $end = $opts->getValue( 'end' );
+ if ( $start !== '' && $end !== '' && $start > $end ) {
+ $temp = $end;
+ $end = $start;
+ $start = $temp;
+
+ $opts->setValue( 'start', $start, true );
+ $opts->setValue( 'end', $end, true );
+ }
+
$opts->validateIntBounds( 'limit', 0, 500 );
$this->opts = $opts;
'default' => $this->opts->getValue( 'offset' ),
'name' => 'offset',
],
+
+ 'start' => [
+ 'type' => 'date',
+ 'label-message' => 'date-range-from',
+ 'name' => 'start',
+ ],
+
+ 'end' => [
+ 'type' => 'date',
+ 'label-message' => 'date-range-to',
+ 'name' => 'end',
+ ],
];
if ( $this->getConfig()->get( 'MiserMode' ) ) {
*/
use MediaWiki\MediaWikiServices;
-class NewFilesPager extends ReverseChronologicalPager {
+class NewFilesPager extends RangeChronologicalPager {
/**
* @var ImageGalleryBase
* @param FormOptions $opts
*/
function __construct( IContextSource $context, FormOptions $opts ) {
- $this->opts = $opts;
+ parent::__construct( $context );
+ $this->opts = $opts;
$this->setLimit( $opts->getValue( 'limit' ) );
- parent::__construct( $context );
+ $startTimestamp = '';
+ $endTimestamp = '';
+ if ( $opts->getValue( 'start' ) ) {
+ $startTimestamp = $opts->getValue( 'start' ) . ' 00:00:00';
+ }
+ if ( $opts->getValue( 'end' ) ) {
+ $endTimestamp = $opts->getValue( 'end' ) . ' 23:59:59';
+ }
+ $this->getDateRangeCond( $startTimestamp, $endTimestamp );
}
function getQueryInfo() {
$this->setToken(); // init token
}
+ if ( !is_string( $this->mName ) ) {
+ throw new RuntimeException( "User name field is not set." );
+ }
+
$this->mTouched = $this->newTouchedTimestamp();
$noPass = PasswordFactory::newInvalidPassword()->toString();
],
'scripts' => 'resources/src/mediawiki/mediawiki.notification.js',
'dependencies' => [
- 'mediawiki.page.startup',
'mediawiki.util',
],
'targets' => [ 'desktop', 'mobile' ],
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js',
'dependencies' => [
'mediawiki.util',
- 'mediawiki.page.startup',
'user.options',
],
],
],
'mediawiki.page.startup' => [
'scripts' => 'resources/src/mediawiki/page/startup.js',
- 'dependencies' => 'mediawiki.util',
'targets' => [ 'desktop', 'mobile' ],
],
'mediawiki.page.patrol.ajax' => [
'scripts' => 'resources/src/mediawiki/page/patrol.ajax.js',
'dependencies' => [
- 'mediawiki.page.startup',
'mediawiki.api',
'mediawiki.util',
'mediawiki.Title',
'mediawiki.page.watch.ajax' => [
'scripts' => 'resources/src/mediawiki/page/watch.js',
'dependencies' => [
- 'mediawiki.page.startup',
'mediawiki.api.watch',
'mediawiki.notify',
'mediawiki.util',
'mediawiki.special.movePage.styles' => [
'styles' => 'resources/src/mediawiki.special/mediawiki.special.movePage.css',
],
+ 'mediawiki.special.newFiles' => [
+ 'scripts' => 'resources/src/mediawiki.special/mediawiki.special.newFiles.js',
+ 'dependencies' => [
+ 'mediawiki.widgets.datetime',
+ ],
+ ],
'mediawiki.special.pageLanguage' => [
'scripts' => 'resources/src/mediawiki.special/mediawiki.special.pageLanguage.js',
'dependencies' => [
preview: true,
sectionpreview: section !== '',
disableeditsection: true,
+ useskin: mw.config.get( 'skin' ),
uselang: mw.config.get( 'wgUserLanguage' )
} );
if ( section === 'new' ) {
--- /dev/null
+/*!
+ * JavaScript for Special:NewFiles
+ */
+( function ( mw, $ ) {
+ $( function () {
+ var start = mw.widgets.datetime.DateTimeInputWidget.static.infuse( 'mw-input-start' ),
+ end = mw.widgets.datetime.DateTimeInputWidget.static.infuse( 'mw-input-end' ),
+ temp;
+
+ // If the start date comes after the end date, swap the two values.
+ // This swap is already done internally when the form is submitted with a start date that
+ // comes after the end date, but this swap makes the change visible in the HTMLForm.
+ if ( start.getValue() !== '' &&
+ end.getValue() !== '' &&
+ start.getValue() > end.getValue() ) {
+ temp = start.getValue();
+ start.setValue( end.getValue() );
+ end.setValue( temp );
+ }
+ } );
+}( mediaWiki, jQuery ) );
*/
var util = {
- /**
- * Initialisation
- * (don't call before document ready)
- */
- init: function () {
- util.$content = ( function () {
- var i, l, $node, selectors;
-
- selectors = [
- // The preferred standard is class "mw-body".
- // You may also use class "mw-body mw-body-primary" if you use
- // mw-body in multiple locations. Or class "mw-body-primary" if
- // you use mw-body deeper in the DOM.
- '.mw-body-primary',
- '.mw-body',
-
- // If the skin has no such class, fall back to the parser output
- '#mw-content-text',
-
- // Should never happen... well, it could if someone is not finished writing a
- // skin and has not yet inserted bodytext yet.
- 'body'
- ];
-
- for ( i = 0, l = selectors.length; i < l; i++ ) {
- $node = $( selectors[ i ] );
- if ( $node.length ) {
- return $node.first();
- }
- }
-
- // Preserve existing customized value in case it was preset
- return util.$content;
- }() );
- },
-
/* Main body */
/**
/**
* The content wrapper of the skin (e.g. `.mw-body`).
*
- * Populated on document ready by #init. To use this property,
+ * Populated on document ready. To use this property,
* wait for `$.ready` and be sure to have a module dependency on
- * `mediawiki.util` and `mediawiki.page.startup` which will ensure
- * your document ready handler fires after #init.
+ * `mediawiki.util` which will ensure
+ * your document ready handler fires after initialization.
*
* Because of the lazy-initialised nature of this property,
* you're discouraged from using it.
return true;
}, 'Use mw.notify instead.' );
+ /**
+ * Initialisation of mw.util.$content
+ */
+ function init() {
+ util.$content = ( function () {
+ var i, l, $node, selectors;
+
+ selectors = [
+ // The preferred standard is class "mw-body".
+ // You may also use class "mw-body mw-body-primary" if you use
+ // mw-body in multiple locations. Or class "mw-body-primary" if
+ // you use mw-body deeper in the DOM.
+ '.mw-body-primary',
+ '.mw-body',
+
+ // If the skin has no such class, fall back to the parser output
+ '#mw-content-text'
+ ];
+
+ for ( i = 0, l = selectors.length; i < l; i++ ) {
+ $node = $( selectors[ i ] );
+ if ( $node.length ) {
+ return $node.first();
+ }
+ }
+
+ // Should never happen... well, it could if someone is not finished writing a
+ // skin and has not yet inserted bodytext yet.
+ return $( 'body' );
+ }() );
+ }
+
+ /**
+ * Former public initialisation. Now a no-op function.
+ *
+ * @method util_init
+ * @deprecated since 1.30
+ */
+ mw.log.deprecate( util, 'init', $.noop, 'Remove the call of mw.util.init().', 'mw.util.init' );
+
+ $( init );
+
mw.util = util;
module.exports = util;
( function ( mw, $ ) {
- mw.page = {};
-
$( function () {
var $diff;
- mw.util.init();
/**
* Fired when wiki content is being added to the DOM
}
// Expose public methods
- mw.page.watch = {
- updateWatchLink: updateWatchLink
+ mw.page = {
+ watch: {
+ updateWatchLink: updateWatchLink
+ }
};
$( function () {
<pre>Foo →bar</pre>
!! html/parsoid
-<pre about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"#tag:pre","function":"#tag"},"params":{"1":{"wt":"Foo <nowiki>&rarr;bar</nowiki>"}},"i":0}}]}'>Foo <span typeof="mw:Entity">→</span>bar</pre>
+<pre about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"#tag:pre","function":"tag"},"params":{"1":{"wt":"Foo <nowiki>&rarr;bar</nowiki>"}},"i":0}}]}'>Foo <span typeof="mw:Entity">→</span>bar</pre>
!! end
## Don't expect this to rt, Parsoid drops the unmatched closing pre tags that
<p>{{echo|[[Foo}}</p>
!! end
+!! test
+Wikilinks with embedded newlines are not broken
+!! wikitext
+{{echo|[[ Foo
+B
+C]]}}
+!! html/php
+<p>[[ Foo
+B
+C]]
+</p>
+!! html/parsoid
+<p typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[[ Foo\nB\nC]]"}},"i":0}}]}'>[[ Foo B C]]</p>
+!! end
+
!! test
Broken templates
!! options
|}
!! html/parsoid
<table>
-<tbody><tr><td> Test <ref about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"#tag:ref","function":"#tag"},"params":{"1":{"wt":"One two \"[[three]]\" four"}},"i":0}}]}'>One two "<a rel="mw:WikiLink" href="./Three" title="Three">three</a>" four</ref></td></tr>
+<tbody><tr><td> Test <ref about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1"}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"#tag:ref","function":"tag"},"params":{"1":{"wt":"One two \"[[three]]\" four"}},"i":0}}]}'>One two "<a rel="mw:WikiLink" href="./Three" title="Three">three</a>" four</ref></td></tr>
</tbody></table>
!! end
!!end
+###
+### Preprocessor precedence tests
+### See: https://www.mediawiki.org/wiki/Preprocessor_ABNF
+###
+##{{[[-{{{{{{[[Foo|bar}}]]}-}}}}}]]
+!! test
+Preprocessor precedence 1: link is rightmost opening
+!! wikitext
+{{[[Foo|bar}}]]
+
+But close-brace is not a valid character in a link title:
+{{[[Foo}}|bar]]
+
+However, we can still tell this was handled as a link in the preprocessor:
+{{echo|[[Foo}}|bar]]|bat}}
+!! html
+<p>{{<a href="/wiki/Foo" title="Foo">bar}}</a>
+</p><p>But close-brace is not a valid character in a link title:
+{{[[Foo}}|bar]]
+</p><p>However, we can still tell this was handled as a link in the preprocessor:
+[[Foo}}|bar]]
+</p>
+!! end
+
+!! test
+Preprocessor precedence 2: template is rightmost opening
+!! options
+language=zh
+!! wikitext
+-{{echo|foo}-}}-
+!! html
+<p>-foo}--
+</p>
+!! end
+
+!! test
+Preprocessor precedence 3: language converter is rightmost opening
+!! options
+language=zh
+!! wikitext
+{{echo|hi}}
+
+{{-{R|echo|hi}}}-
+
+[[-{R|raw]]}-
+!! html
+<p>hi
+</p><p>{{echo|hi}}
+</p><p>[[raw]]
+</p>
+!! end
+
+!! test
+Preprocessor precedence 4: left-most angle bracket
+!! options
+language=zh
+!! wikitext
+<!--{raw}-->
+!! html
+!! end
+
+!! article
+Template:Precedence5
+!! text
+{{{{{1}}}}}
+!! endarticle
+
+!! test
+Preprocessor precedence 5: tplarg takes precedence over template
+!! wikitext
+{{Precedence5|Bullet}}
+!! html
+<ul><li> Bar</li></ul>
+
+!! end
+
+!! test
+Preprocessor precedence 6: broken link is rightmost opening
+!! wikitext
+{{echo|[[Foo}}
+
+{{echo|[[Foo|bar|bat=baz}}
+!! html
+<p>{{echo|[[Foo}}
+</p><p>{{echo|[[Foo|bar|bat=baz}}
+</p>
+!! end
+
+# This next test exposes a difference between PHP and Parsoid:
+# Given [[Foo|{{echo|Bar]]x}}y]]z:
+# 1) Both PHP and Parsoid ignore the `]]` inside the `echo` in the
+# "preprocessor" stage. The `{{echo` extends until the `x}}`, and the
+# outer `[[Foo` extends until the `y]]`
+# 2a) But then the PHP preprocessor emits `[[Foo|Bar]]xy]]z` as an
+# intermediate result (after template expansion), and link processing
+# happens on this intermediate result, which moves the wikilink
+# boundary leftward to `[[Foo|Bar]]`
+# 2b) Parsoid works in a single step, so it's going to keep the
+# wikilink as extending to the `y]]`
+# 3a) Then PHP does linktrail processing which slurps up the trailing
+# `xy` inside the link.
+# 3b) Parsoid will do linktrail processing to slurp up the trailing
+# `z` inside the link.
+# This is "correct" behavior. Parsoid's basic worldview is that the
+# `]]` inside the template shouldn't be allowed to leak out to affect
+# the surrounding wikilink. PHP may match Parsoid (in the future)
+# if you use {{#balance}} (T114445).
+
+!! test
+Preprocessor precedence 7: broken template is rightmost opening
+!! wikitext
+[[Foo|{{echo|Bar]]
+
+[[Foo|{{echo|Bar]]-x}}-y]]-z
+
+Careful: linktrails can move the end of the wikilink:
+[[Foo|{{echo|y']]a}}l]]l
+!! html
+<p><a href="/wiki/Foo" title="Foo">{{echo|Bar</a>
+</p><p><a href="/wiki/Foo" title="Foo">Bar</a>-x-y]]-z
+</p><p>Careful: linktrails can move the end of the wikilink:
+<a href="/wiki/Foo" title="Foo">y'al</a>]]l
+</p>
+!! end
+
+!! test
+Preprocessor precedence 8: broken language converter is rightmost opening
+!! options
+language=zh
+!! wikitext
+[[Foo-{R|raw]]
+!! html
+<p>[[Foo-{R|raw]]
+</p>
+!! end
+
+!! article
+Template:Preprocessor_precedence_9
+!! text
+;4: {{{{1}}}}
+;5: {{{{{2}}}}}
+;6: {{{{{{3}}}}}}
+;7: {{{{{{{4}}}}}}}
+!! endarticle
+
+!! test
+Preprocessor precedence 9: groups of braces
+!! wikitext
+{{Preprocessor precedence 9|Four|Bullet|1|2}}
+!! html
+<dl><dt>4</dt>
+<dd> {Four}</dd>
+<dt>5</dt>
+<dd> </dd></dl>
+<ul><li> Bar</li></ul>
+<dl><dt>6</dt>
+<dd> Four</dd>
+<dt>7</dt>
+<dd> {Bullet}</dd></dl>
+
+!! end
+
+!! article
+Template:Preprocessor_precedence_10
+!! text
+;1: -{R|raw}-
+;2: -{{Bullet}}-
+;3: -{{{1}}}-
+;4: -{{{{2}}}}-
+;5: -{{{{{3}}}}}-
+;6: -{{{{{{4}}}}}}-
+;7: -{{{{{{{5}}}}}}}-
+!! endarticle
+
+!! test
+Preprocessor precedence 10: groups of braces with leading dash
+!! options
+language=zh
+!! wikitext
+{{Preprocessor precedence 10|Three|raw2|Bullet|1|2}}
+!! html
+<dl><dt>1</dt>
+<dd> raw</dd>
+<dt>2</dt>
+<dd> -</dd></dl>
+<ul><li> Bar-</li></ul>
+<dl><dt>3</dt>
+<dd> -Three-</dd>
+<dt>4</dt>
+<dd> raw2</dd>
+<dt>5</dt>
+<dd> -</dd></dl>
+<ul><li> Bar-</li></ul>
+<dl><dt>6</dt>
+<dd> -Three-</dd>
+<dt>7</dt>
+<dd> raw2</dd></dl>
+
+!! end
+
+!! test
+Preprocessor precedence 11: found during visual diff testing
+!! wikitext
+{{#tag:span|-{{#tag:span|-{{echo|x}}}}}}
+
+{{echo|-{{echo|-{{echo|x}}}}}}
+
+{{echo|-{{echo|x}}}}
+!! html
+<p><span>-<span>-x</span></span>
+</p><p>--x
+</p><p>-x
+</p>
+!! end
+
+!! test
+Preprocessor precedence 12: broken language converter closed by brace.
+!! wikitext
+This form breaks the template, which is unfortunate:
+* {{echo|foo-{bar}bat}}
+
+But if the broken language converter markup is inside an extension
+tag, nothing bad happens:
+* <nowiki>foo-{bar}bat</nowiki>
+* {{echo|<nowiki>foo-{bar}bat</nowiki>}}
+* <pre>foo-{bar}bat</pre>
+* {{echo|<pre>foo-{bar}bat</pre>}}
+
+<tag>foo-{bar}bat</tag>
+{{echo|<tag>foo-{bar}bat</tag>}}
+
+!! html+tidy
+<p>This form breaks the template, which is unfortunate:</p>
+<ul>
+<li>{{echo|foo-{bar}bat}}</li>
+</ul>
+<p>But if the broken language converter markup is inside an extension tag, nothing bad happens:</p>
+<ul>
+<li>foo-{bar}bat</li>
+<li>foo-{bar}bat</li>
+<li>
+<pre>
+foo-{bar}bat
+</pre></li>
+<li>
+<pre>
+foo-{bar}bat
+</pre></li>
+</ul>
+<pre>
+'foo-{bar}bat'
+array (
+)
+</pre>
+<pre>
+'foo-{bar}bat'
+array (
+)
+</pre>
+!! end
+
+!! test
+Preprocessor precedence, 13: broken language converter in external link
+!! wikitext
+* [http://example.com/-{foo Example in URL]
+* [http://example.com Example in -{link} description]
+* {{echo|[http://example.com/-{foo Breaks template, however]}}
+!! html+tidy
+<ul>
+<li><a rel="nofollow" class="external text" href="http://example.com/-{foo">Example in URL</a></li>
+<li><a rel="nofollow" class="external text" href="http://example.com">Example in -{link} description</a></li>
+<li>{{echo|<a rel="nofollow" class="external text" href="http://example.com/-{foo">Breaks template, however</a>}}</li>
+</ul>
+!! end
+
+!! test
+Preprocessor precedence, 14: broken language converter in comment
+!! wikitext
+* <!--{{foo}}--> ...should be ok
+* <!---{{foo}}--> ...extra dashes
+* {{echo|foo<!-- -{bar} -->bat}} ...should be ok
+!! html+tidy
+<ul>
+<li>...should be ok</li>
+<li>...extra dashes</li>
+<li>foobat ...should be ok</li>
+</ul>
+!! end
+
+!! test
+Preprocessor precedence, 15: broken brace markup in headings
+!! wikitext
+__NOTOC__ __NOEDITSECTION__
+===1 foo[bar 1===
+1
+===2 foo[[bar 2===
+2
+===3 foo{bar 3===
+3
+===4 foo{{bar 4===
+4
+===5 foo{{{bar 5===
+5
+===6 foo-{bar 6===
+6
+!! html+tidy
+<h3><span class="mw-headline" id="1_foo.5Bbar_1">1 foo[bar 1</span></h3>
+<p>1</p>
+<h3><span class="mw-headline" id="2_foo.5B.5Bbar_2">2 foo[[bar 2</span></h3>
+<p>2</p>
+<h3><span class="mw-headline" id="3_foo.7Bbar_3">3 foo{bar 3</span></h3>
+<p>3</p>
+<h3><span class="mw-headline" id="4_foo.7B.7Bbar_4">4 foo{{bar 4</span></h3>
+<p>4</p>
+<h3><span class="mw-headline" id="5_foo.7B.7B.7Bbar_5">5 foo{{{bar 5</span></h3>
+<p>5</p>
+<h3><span class="mw-headline" id="6_foo-.7Bbar_6">6 foo-{bar 6</span></h3>
+<p>6</p>
+!! end
+
###
### Token Stream Patcher tests
###
| is not a magic word here but | is still a magic word here
</p>
!! html/parsoid
-<p><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"!","href":"./Template:!"},"params":{},"i":0}}]}'>|</span> is a magic word there and <span about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"!","href":"./Template:!"},"params":{},"i":0}}]}'>|</span> is still a magic word here
-| is not a magic word here but <span about="#mwt3" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"!","href":"./Template:!"},"params":{},"i":0}}]}'>|</span> is still a magic word here</p>
-
+<p><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"!","function":"!"},"params":{},"i":0}}]}'>|</span> is a magic word there and <span about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"!","function":"!"},"params":{},"i":0}}]}'>|</span> is still a magic word here
+| is not a magic word here but <span about="#mwt3" typeof="mw:Transclusion" data-parsoid='{"pi":[[]]}' data-mw='{"parts":[{"template":{"target":{"wt":"!","function":"!"},"params":{},"i":0}}]}'>|</span> is still a magic word here</p>
!! end
!! test
</p>
!! end
+!! test
+Nested markup inside raw output of variant escape tags (R flag)
+!! options
+language=zh variant=zh-tw
+!! wikitext
+Nested raw: -{R|nested -{zh:China;zh-tw:Taiwan}- nested}-
+!! html
+<p>Nested raw: nested Taiwan nested
+</p>
+!! end
+
+!! test
+Templates inside raw output of variant escape tags (R flag)
+!! options
+language=zh variant=zh-tw
+!! wikitext
+Nested raw: -{R|nested {{echo|hi}} templates}-
+!! html
+<p>Nested raw: nested hi templates
+</p>
+!! end
+
!! test
Strings evaluating false shouldn't be ignored by Language converter (T51072)
!! options
</p>
!! end
-# FIXME: This test is currently broken in the PHP parser T153761
!! test
T146304: Don't break template parsing if language converter markup is in the parameter.
!! options
language=sr variant=sr-ec
-disabled
!! wikitext
{{echo|-{R|foo}-}}
!! html/php
}}
</table>
!! html/parsoid
-<table about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["<table>\n",{"template":{"target":{"wt":"#if:","function":"#if"},"params":{"1":{"wt":"\n<td>foo</td>\n"}},"i":0}},"\n</table>"]}' data-parsoid='{"stx":"html","pi":[[{"k":"1"}]]}'>
+<table about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["<table>\n",{"template":{"target":{"wt":"#if:","function":"if"},"params":{"1":{"wt":"\n<td>foo</td>\n"}},"i":0}},"\n</table>"]}' data-parsoid='{"stx":"html","pi":[[{"k":"1"}]]}'>
</table>
!! end
$this->insertPage( 'Example Foo' );
$this->insertPage( 'Example Foo/Bar' );
$this->insertPage( 'Example/Baz' );
+ $this->insertPage( 'Sample' );
+ $this->insertPage( 'Sample Ban' );
+ $this->insertPage( 'Sample Eat' );
+ $this->insertPage( 'Sample Who' );
+ $this->insertPage( 'Sample Zoo' );
$this->insertPage( 'Redirect test', '#REDIRECT [[Redirect Test]]' );
$this->insertPage( 'Redirect Test' );
$this->insertPage( 'Redirect Test Worse Result' );
] ],
[ [
'Main namespace with title prefix',
- 'query' => 'Ex',
+ 'query' => 'Sa',
'results' => [
- 'Example',
- 'Example/Baz',
- 'Example Bar',
+ 'Sample',
+ 'Sample Ban',
+ 'Sample Eat',
],
// Third result when testing offset
'offsetresult' => [
- 'Example Foo',
+ 'Sample Who',
],
] ],
[ [
$results = array_map( function( Title $t ) {
return $t->getPrefixedText();
}, $results );
+
$this->assertEquals(
$case['results'],
$results,
* @covers UserGroupMembership::delete
*/
public function testAddAndRemoveGroups() {
- $user = new User;
- $user->addToDatabase();
+ $user = $this->getMutableTestUser()->getUser();
// basic tests
$ugm = new UserGroupMembership( $user->getId(), 'unittesters' );
$this->setUpPermissionGlobals();
- $this->user = new User;
- $this->user->addToDatabase();
- $this->user->addGroup( 'unittesters' );
+ $this->user = $this->getTestUser( [ 'unittesters' ] )->getUser();
}
private function setUpPermissionGlobals() {
* @covers User::getRights
*/
public function testUserGetRightsHooks() {
- $user = new User;
- $user->addToDatabase();
- $user->addGroup( 'unittesters' );
- $user->addGroup( 'testwriters' );
+ $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
$userWrapper = TestingAccessWrapper::newFromObject( $user );
$rights = $user->getRights();