'Parser' => __DIR__ . '/includes/parser/Parser.php',
'ParserCache' => __DIR__ . '/includes/parser/ParserCache.php',
'ParserDiffTest' => __DIR__ . '/includes/parser/ParserDiffTest.php',
+ 'ParserFactory' => __DIR__ . '/includes/parser/ParserFactory.php',
'ParserOptions' => __DIR__ . '/includes/parser/ParserOptions.php',
'ParserOutput' => __DIR__ . '/includes/parser/ParserOutput.php',
'ParsoidVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/ParsoidVirtualRESTService.php',
use ObjectCache;
use Parser;
use ParserCache;
+use ParserFactory;
use PasswordFactory;
use ProxyLookup;
use SearchEngine;
return $this->getService( 'ParserCache' );
}
+ /**
+ * @since 1.32
+ * @return ParserFactory
+ */
+ public function getParserFactory() {
+ return $this->getService( 'ParserFactory' );
+ }
+
/**
* @since 1.32
* @return PasswordFactory
use MediaWiki\Storage\RevisionStore;
use MediaWiki\Storage\RevisionStoreFactory;
use MediaWiki\Storage\SqlBlobStore;
-use Wikimedia\ObjectFactory;
return [
'ActorMigration' => function ( MediaWikiServices $services ) : ActorMigration {
},
'Parser' => function ( MediaWikiServices $services ) : Parser {
- $conf = $services->getMainConfig()->get( 'ParserConf' );
- return ObjectFactory::constructClassInstance( $conf['class'],
- [ $conf, $services->getMagicWordFactory() ] );
+ return $services->getParserFactory()->create();
},
'ParserCache' => function ( MediaWikiServices $services ) : ParserCache {
);
},
+ 'ParserFactory' => function ( MediaWikiServices $services ) : ParserFactory {
+ return new ParserFactory(
+ $services->getMainConfig()->get( 'ParserConf' ),
+ $services->getMagicWordFactory(),
+ $services->getContentLanguage(),
+ wfUrlProtocols()
+ );
+ },
+
'PasswordFactory' => function ( MediaWikiServices $services ) : PasswordFactory {
$config = $services->getMainConfig();
return new PasswordFactory(
if ( $this->reader->getAttribute( 'viewBox' ) ) {
// min-x min-y width height
- $viewBox = preg_split( '/\s+/', trim( $this->reader->getAttribute( 'viewBox' ) ) );
+ $viewBox = preg_split( '/\s*[\s,]\s*/', trim( $this->reader->getAttribute( 'viewBox' ) ) );
if ( count( $viewBox ) == 4 ) {
$viewWidth = $this->scaleSVGUnit( $viewBox[2] );
$viewHeight = $this->scaleSVGUnit( $viewBox[3] );
}
public static function ns( $parser, $part1 = '' ) {
- global $wgContLang;
if ( intval( $part1 ) || $part1 == "0" ) {
$index = intval( $part1 );
} else {
- $index = $wgContLang->getNsIndex( str_replace( ' ', '_', $part1 ) );
+ $index = $parser->getContentLanguage()->getNsIndex( str_replace( ' ', '_', $part1 ) );
}
if ( $index !== false ) {
- return $wgContLang->getFormattedNsText( $index );
+ return $parser->getContentLanguage()->getFormattedNsText( $index );
} else {
return [ 'found' => false ];
}
}
public static function lcfirst( $parser, $s = '' ) {
- global $wgContLang;
- return $wgContLang->lcfirst( $s );
+ return $parser->getContentLanguage()->lcfirst( $s );
}
public static function ucfirst( $parser, $s = '' ) {
- global $wgContLang;
- return $wgContLang->ucfirst( $s );
+ return $parser->getContentLanguage()->ucfirst( $s );
}
/**
* @return string
*/
public static function lc( $parser, $s = '' ) {
- global $wgContLang;
- return $parser->markerSkipCallback( $s, [ $wgContLang, 'lc' ] );
+ return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'lc' ] );
}
/**
* @return string
*/
public static function uc( $parser, $s = '' ) {
- global $wgContLang;
- return $parser->markerSkipCallback( $s, [ $wgContLang, 'uc' ] );
+ return $parser->markerSkipCallback( $s, [ $parser->getContentLanguage(), 'uc' ] );
}
public static function localurl( $parser, $s = '', $arg = null ) {
* @return string
*/
public static function pagesincategory( $parser, $name = '', $arg1 = null, $arg2 = null ) {
- global $wgContLang;
static $magicWords = null;
if ( is_null( $magicWords ) ) {
$magicWords = $parser->getMagicWordFactory()->newArray( [
if ( !$title ) { # invalid title
return self::formatRaw( 0, $raw, $parser->getFunctionLang() );
}
- $wgContLang->findVariantLink( $name, $title, true );
+ $parser->getContentLanguage()->findVariantLink( $name, $title, true );
// Normalize name for cache
$name = $title->getDBkey();
return;
}
- global $wgContLang;
-
$colours = [];
$linkCache = MediaWikiServices::getInstance()->getLinkCache();
$output = $this->parent->getOutput();
}
# Do a second query for different language variants of links and categories
- if ( $wgContLang->hasVariants() ) {
+ if ( $this->parent->getContentLanguage()->hasVariants() ) {
$this->doVariants( $colours );
}
* @param array &$colours
*/
protected function doVariants( &$colours ) {
- global $wgContLang;
$linkBatch = new LinkBatch();
$variantMap = []; // maps $pdbkey_Variant => $keys (of link holders)
$output = $this->parent->getOutput();
}
// Now do the conversion and explode string to text of titles
- $titlesAllVariants = $wgContLang->autoConvertToAllVariants( rtrim( $titlesToBeConverted, "\0" ) );
+ $titlesAllVariants = $this->parent->getContentLanguage()->
+ autoConvertToAllVariants( rtrim( $titlesToBeConverted, "\0" ) );
$allVariantsName = array_keys( $titlesAllVariants );
foreach ( $titlesAllVariants as &$titlesVariant ) {
$titlesVariant = explode( "\0", $titlesVariant );
foreach ( $output->getCategoryLinks() as $category ) {
$categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $category );
$linkBatch->addObj( $categoryTitle );
- $variants = $wgContLang->autoConvertToAllVariants( $category );
+ $variants = $this->parent->getContentLanguage()->autoConvertToAllVariants( $category );
foreach ( $variants as $variant ) {
if ( $variant !== $category ) {
$variantTitle = Title::makeTitleSafe( NS_CATEGORY, $variant );
/** @var MagicWordFactory */
private $magicWordFactory;
+ /** @var Language */
+ private $contLang;
+
+ /** @var ParserFactory */
+ private $factory;
+
/**
- * @param array $conf
+ * @param array $conf See $wgParserConf documentation
* @param MagicWordFactory|null $magicWordFactory
+ * @param Language|null $contLang Content language
+ * @param ParserFactory|null $factory
+ * @param string|null $urlProtocols As returned from wfUrlProtocols()
*/
- public function __construct( $conf = [], MagicWordFactory $magicWordFactory = null ) {
+ public function __construct(
+ array $conf = [], MagicWordFactory $magicWordFactory = null, Language $contLang = null,
+ ParserFactory $factory = null, $urlProtocols = null
+ ) {
$this->mConf = $conf;
- $this->mUrlProtocols = wfUrlProtocols();
+ $this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
$this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
self::EXT_LINK_ADDR .
self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
}
wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
- $this->magicWordFactory = $magicWordFactory;
- if ( !$magicWordFactory ) {
- $this->magicWordFactory = MediaWikiServices::getInstance()->getMagicWordFactory();
- }
+ $this->magicWordFactory = $magicWordFactory ??
+ MediaWikiServices::getInstance()->getMagicWordFactory();
+
+ $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
+
+ $this->factory = $factory ?? MediaWikiServices::getInstance()->getParserFactory();
}
/**
* @return Language
*/
public function getContentLanguage() {
- return $this->magicWordFactory->getContentLanguage();
+ return $this->contLang;
}
/**
if ( $useLinkPrefixExtension ) {
# Match the end of a line for a word that's not followed by whitespace,
# e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
- $charset = $this->getContentLanguage()->linkPrefixCharset();
+ $charset = $this->contLang->linkPrefixCharset();
$e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
}
break;
case 'namespace':
$value = str_replace( '_', ' ',
- $this->getContentLanguage()->getNsText( $this->mTitle->getNamespace() ) );
+ $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'namespacee':
- $value = wfUrlencode( $this->getContentLanguage()->
- getNsText( $this->mTitle->getNamespace() ) );
+ $value = wfUrlencode( $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'namespacenumber':
$value = $this->mTitle->getNamespace();
if ( !$this->getRevisionObject() ) {
# Get the timezone-adjusted timestamp $mtts seconds in the future
$resThen = substr(
- $this->getContentLanguage()->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
+ $this->contLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
$start,
$len
);
$function = $this->mFunctionSynonyms[1][$function];
} else {
# Case insensitive functions
- $function = $this->getContentLanguage()->lc( $function );
+ $function = $this->contLang->lc( $function );
if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
$function = $this->mFunctionSynonyms[0][$function];
} else {
*/
private function pstPass2( $text, $user ) {
# Note: This is the timestamp saved as hardcoded wikitext to the database, we use
- # $this->getContentLanguage() here in order to give everyone the same signature and use the
- # default one rather than the one selected in each user's preferences. (see also T14815)
+ # $this->contLang here in order to give everyone the same signature and use the default one
+ # rather than the one selected in each user's preferences. (see also T14815)
$ts = $this->mOptions->getTimestamp();
$timestamp = MWTimestamp::getLocalInstance( $ts );
$ts = $timestamp->format( 'YmdHis' );
$tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
- $d = $this->getContentLanguage()->timeanddate( $ts, false, false ) . " ($tzMsg)";
+ $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
# Variable replacement
# Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
foreach ( $synonyms as $syn ) {
# Case
if ( !$sensitive ) {
- $syn = $this->getContentLanguage()->lc( $syn );
+ $syn = $this->contLang->lc( $syn );
}
# Add leading hash
if ( !( $flags & self::SFH_NO_HASH ) ) {
# Since this value will be saved into the parser cache, served
# to other users, and potentially even used inside links and such,
# it needs to be consistent for all visitors.
- $this->mRevisionTimestamp = $this->getContentLanguage()->userAdjust( $timestamp, '' );
+ $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
}
return $this->mRevisionTimestamp;
* @return Parser A parser object that is not parsing anything
*/
public function getFreshParser() {
- global $wgParserConf;
if ( $this->mInParse ) {
- return new $wgParserConf['class']( $wgParserConf );
+ return $this->factory->create();
} else {
return $this;
}
--- /dev/null
+<?php
+
+/**
+ * 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
+ * @ingroup Parser
+ */
+
+/**
+ * @since 1.32
+ */
+class ParserFactory {
+ /** @var array */
+ private $conf;
+
+ /** @var MagicWordFactory */
+ private $magicWordFactory;
+
+ /** @var Language */
+ private $contLang;
+
+ /** @var string */
+ private $urlProtocols;
+
+ /**
+ * @param array $conf See $wgParserConf documentation
+ * @param MagicWordFactory $magicWordFactory
+ * @param Language $contLang Content language
+ * @param string $urlProtocols As returned from wfUrlProtocols()
+ * @since 1.32
+ */
+ public function __construct(
+ array $conf, MagicWordFactory $magicWordFactory, Language $contLang, $urlProtocols
+ ) {
+ $this->conf = $conf;
+ $this->magicWordFactory = $magicWordFactory;
+ $this->contLang = $contLang;
+ $this->urlProtocols = $urlProtocols;
+ }
+
+ /**
+ * @return Parser
+ * @since 1.32
+ */
+ public function create() : Parser {
+ return new Parser( $this->conf, $this->magicWordFactory, $this->contLang, $this,
+ $this->urlProtocols );
+ }
+}
<?php
+
+use MediaWiki\MediaWikiServices;
+
/**
* Parse some wikitext.
*
}
protected function initParser() {
- global $wgParserConf;
- $parserClass = $wgParserConf['class'];
- $this->parser = new $parserClass();
+ $this->parser = MediaWikiServices::getInstance()->getParserFactory()->create();
}
/**
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox=" 0, 0 ,349.46883 , 405.12272 ">
+</svg>
*
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-class MediaWikiVersionFetcherTest extends PHPUnit\Framework\TestCase {
+class MediaWikiVersionFetcherTest extends MediaWikiTestCase {
use MediaWikiCoversValidator;
public function testReturnsResult() {
+ global $wgVersion;
$versionFetcher = new MediaWikiVersionFetcher();
- $this->assertInternalType( 'string', $versionFetcher->fetchVersion() );
+ $this->assertSame( $wgVersion, $versionFetcher->fetchVersion() );
}
}
<?php
-use Wikimedia\ObjectFactory;
+use MediaWiki\MediaWikiServices;
use Wikimedia\TestingAccessWrapper;
/**
}
public function testRawHtmlInMsg() {
- global $wgParserConf;
$this->setMwGlobals( 'wgRawHtml', true );
// We have to reset the core hook registration.
// to register the html hook
MessageCache::destroyInstance();
$this->setMwGlobals( 'wgParser',
- ObjectFactory::constructClassInstance( $wgParserConf['class'], [ $wgParserConf ] )
- );
+ MediaWikiServices::getInstance()->getParserFactory()->create() );
$msg = new RawMessage( '<html><script>alert("xss")</script></html>' );
$txt = '<span class="error"><html> tags cannot be' .
parent::setUp();
$this->setMwGlobals( [ 'wgAuth' => null ] );
- $this->stashMwGlobals( [ 'wgHooks' ] );
}
/**
* @return object $mock->expects( $expect )->method( ... ).
*/
protected function hook( $hook, $expect ) {
- global $wgHooks;
$mock = $this->getMockBuilder( __CLASS__ )
->setMethods( [ "on$hook" ] )
->getMock();
- $wgHooks[$hook] = [ $mock ];
+ $this->setTemporaryHook( $hook, $mock );
return $mock->expects( $expect )->method( "on$hook" );
}
);
}
- public function testSetDefaultUserOptions() {
+ /**
+ * @dataProvider provideSetDefaultUserOptions
+ */
+ public function testSetDefaultUserOptions(
+ $contLang, $useContextLang, $expectedLang, $expectedVariant
+ ) {
$this->initializeManager();
+ $this->setContentLang( $contLang );
$context = \RequestContext::getMain();
$reset = new ScopedCallback( [ $context, 'setLanguage' ], [ $context->getLanguage() ] );
$context->setLanguage( 'de' );
- $this->setContentLang( 'zh' );
-
- $user = \User::newFromName( self::usernameForCreation() );
- $user->addToDatabase();
- $oldToken = $user->getToken();
- $this->managerPriv->setDefaultUserOptions( $user, false );
- $user->saveSettings();
- $this->assertNotEquals( $oldToken, $user->getToken() );
- $this->assertSame( 'zh', $user->getOption( 'language' ) );
- $this->assertSame( 'zh', $user->getOption( 'variant' ) );
$user = \User::newFromName( self::usernameForCreation() );
$user->addToDatabase();
$oldToken = $user->getToken();
- $this->managerPriv->setDefaultUserOptions( $user, true );
+ $this->managerPriv->setDefaultUserOptions( $user, $useContextLang );
$user->saveSettings();
$this->assertNotEquals( $oldToken, $user->getToken() );
- $this->assertSame( 'de', $user->getOption( 'language' ) );
- $this->assertSame( 'zh', $user->getOption( 'variant' ) );
-
- $this->setContentLang( 'fr' );
+ $this->assertSame( $expectedLang, $user->getOption( 'language' ) );
+ $this->assertSame( $expectedVariant, $user->getOption( 'variant' ) );
+ }
- $user = \User::newFromName( self::usernameForCreation() );
- $user->addToDatabase();
- $oldToken = $user->getToken();
- $this->managerPriv->setDefaultUserOptions( $user, true );
- $user->saveSettings();
- $this->assertNotEquals( $oldToken, $user->getToken() );
- $this->assertSame( 'de', $user->getOption( 'language' ) );
- $this->assertSame( null, $user->getOption( 'variant' ) );
+ public function provideSetDefaultUserOptions() {
+ return [
+ [ 'zh', false, 'zh', 'zh' ],
+ [ 'zh', true, 'de', 'zh' ],
+ [ 'fr', true, 'de', null ],
+ ];
}
public function testForcePrimaryAuthenticationProviders() {
$wgGroupPermissions['*']['createaccount'] = true;
$wgGroupPermissions['*']['autocreateaccount'] = false;
- \ObjectCache::$instances[__METHOD__] = new \HashBagOStuff();
+ $this->mergeMwGlobalArrayValue( 'wgObjectCaches',
+ [ __METHOD__ => [ 'class' => 'HashBagOStuff' ] ] );
$this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
// Set up lots of mocks...
'translations' => []
],
],
+ [
+ "$base/comma_separated_viewbox.svg",
+ [
+ 'width' => 512,
+ 'height' => 594,
+ 'originalWidth' => '100%',
+ 'originalHeight' => '100%',
+ 'translations' => []
+ ],
+ ],
];
}
--- /dev/null
+<?php
+
+/**
+ * @covers ParserFactory
+ */
+class ParserFactoryTest extends MediaWikiTestCase {
+ /**
+ * For backwards compatibility, all parameters to the parser constructor are optional and
+ * default to the appropriate global service, so it's easy to forget to update ParserFactory to
+ * actually pass the parameters it's supposed to.
+ */
+ public function testConstructorArgNum() {
+ $factoryConstructor = new ReflectionMethod( 'ParserFactory', '__construct' );
+ $instanceConstructor = new ReflectionMethod( 'Parser', '__construct' );
+ // Subtract one for the ParserFactory itself
+ $this->assertSame( $instanceConstructor->getNumberOfParameters() - 1,
+ $factoryConstructor->getNumberOfParameters(),
+ 'Parser and ParserFactory constructors have an inconsistent number of parameters. ' .
+ 'Did you add a parameter to one and not the other?' );
+ }
+
+ public function testAllArgumentsWerePassed() {
+ $factoryConstructor = new ReflectionMethod( 'ParserFactory', '__construct' );
+ $mocks = [];
+ foreach ( $factoryConstructor->getParameters() as $param ) {
+ $type = (string)$param->getType();
+ if ( $type === 'array' ) {
+ $val = [ 'porcupines will tell me your secrets' . count( $mocks ) ];
+ } elseif ( class_exists( $type ) || interface_exists( $type ) ) {
+ $val = $this->createMock( $type );
+ } elseif ( $type === '' ) {
+ // Optimistically assume a string is okay
+ $val = 'I will de-quill them first' . count( $mocks );
+ } else {
+ $this->fail( "Unrecognized parameter type $type in ParserFactory constructor" );
+ }
+ $mocks[] = $val;
+ }
+
+ $factory = new ParserFactory( ...$mocks );
+ $parser = $factory->create();
+
+ foreach ( ( new ReflectionObject( $parser ) )->getProperties() as $prop ) {
+ $prop->setAccessible( true );
+ foreach ( $mocks as $idx => $mock ) {
+ if ( $prop->getValue( $parser ) === $mock ) {
+ unset( $mocks[$idx] );
+ }
+ }
+ }
+
+ $this->assertCount( 0, $mocks, 'Not all arguments to the ParserFactory constructor were ' .
+ 'found in Parser member variables' );
+ }
+}
<?php
+use MediaWiki\MediaWikiServices;
+
/**
* @group Database
* @group Parser
* @dataProvider provideValidNames
*/
public function testTagHooks( $tag ) {
- global $wgParserConf;
- $parser = new Parser( $wgParserConf );
+ $parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setHook( $tag, [ $this, 'tagCallback' ] );
$parserOutput = $parser->parse(
* @expectedException MWException
*/
public function testBadTagHooks( $tag ) {
- global $wgParserConf;
- $parser = new Parser( $wgParserConf );
+ $parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setHook( $tag, [ $this, 'tagCallback' ] );
$parser->parse(
* @dataProvider provideValidNames
*/
public function testFunctionTagHooks( $tag ) {
- global $wgParserConf;
- $parser = new Parser( $wgParserConf );
+ $parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setFunctionTagHook( $tag, [ $this, 'functionTagCallback' ], 0 );
$parserOutput = $parser->parse(
* @expectedException MWException
*/
public function testBadFunctionTagHooks( $tag ) {
- global $wgParserConf;
- $parser = new Parser( $wgParserConf );
+ $parser = MediaWikiServices::getInstance()->getParserFactory()->create();
$parser->setFunctionTagHook(
$tag,
use MediaWiki\Auth\AuthManager;
use MediaWiki\MediaWikiServices;
use MediaWiki\Preferences\DefaultPreferencesFactory;
-use Wikimedia\ObjectFactory;
use Wikimedia\TestingAccessWrapper;
/**
public function setUp() {
parent::setUp();
- global $wgParserConf;
$this->context = new RequestContext();
$this->context->setTitle( Title::newFromText( self::class ) );
- $this->setMwGlobals( 'wgParser',
- ObjectFactory::constructClassInstance( $wgParserConf['class'], [ $wgParserConf ] )
- );
- $this->config = MediaWikiServices::getInstance()->getMainConfig();
+
+ $services = MediaWikiServices::getInstance();
+
+ $this->setMwGlobals( 'wgParser', $services->getParserFactory()->create() );
+ $this->config = $services->getMainConfig();
}
/**