Yurik has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/332089 )
Change subject: Fix cross-wiki title normalization ...................................................................... Fix cross-wiki title normalization * Remove a nasty hack of using a well known interwiki name * Capitalize title Bug: T155214 Change-Id: I72ca4c5ca36b7e04f1f002ad4df74dff3b2600b8 --- M includes/JCSingleton.php M tests/phpunit/JCTitleParsingTest.php 2 files changed, 84 insertions(+), 37 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/JsonConfig refs/changes/89/332089/1 diff --git a/includes/JCSingleton.php b/includes/JCSingleton.php index 797cd90..73d32a8 100644 --- a/includes/JCSingleton.php +++ b/includes/JCSingleton.php @@ -7,9 +7,11 @@ use Exception; use GenderCache; use Html; +use Interwiki; use Language; use MalformedTitleException; use MapCacheLRU; +use MediaWiki\Interwiki\InterwikiLookup; use MediaWiki\Linker\LinkTarget; use MediaWikiTitleCodec; use TitleParser; @@ -53,6 +55,11 @@ * @var MapCacheLRU[] contains a cache of recently requested content objects as namespace => MapCacheLRU */ static $mapCacheLru = []; + + /** + * @var Language cached invariant language to use for all titles parsing + */ + static $language; /** * @var TitleParser cached invariant title parser @@ -138,6 +145,7 @@ self::getConfVal( $conf, 'cacheKey', '' ); self::getConfVal( $conf, 'flaggedRevs', false ); self::getConfVal( $conf, 'license', false ); + self::getConfVal( $conf, 'capitalize', true ); $islocal = self::getConfVal( $conf, 'isLocal', true ); // Decide if matching configs should be stored on this wiki @@ -171,15 +179,15 @@ // Too lazy to write proper error messages for all parameters. if ( ( isset( $conf->nsTalk ) && !is_string( $conf->nsTalk ) ) || !is_string( $conf->pattern ) || - !is_bool( $islocal ) || !is_int( $conf->cacheExp ) || - !is_string( $conf->cacheKey ) || !is_bool( $conf->flaggedRevs ) + !is_bool( $islocal ) || !is_int( $conf->cacheExp ) || + !is_string( $conf->cacheKey ) || !is_bool( $conf->flaggedRevs ) ) { $warnFunc( "JsonConfig: Invalid type of one of the parameters in \$wgJsonConfigs['$confId'], please check documentation" ); continue; } if ( isset( $remote ) ) { if ( !is_string( $remote->url ) || !is_string( $remote->username ) || - !is_string( $remote->password ) + !is_string( $remote->password ) ) { $warnFunc( "JsonConfig: Invalid type of one of the parameters in \$wgJsonConfigs['$confId']['remote'], please check documentation" ); continue; @@ -187,7 +195,7 @@ } if ( isset( $store ) ) { if ( !is_bool( $store->cacheNewValue ) || !is_string( $store->notifyUrl ) || - !is_string( $store->notifyUsername ) || !is_string( $store->notifyPassword ) + !is_string( $store->notifyUsername ) || !is_string( $store->notifyPassword ) ) { $warnFunc( "JsonConfig: Invalid type of one of the parameters in \$wgJsonConfigs['$confId']['store'], please check documentation" ); continue; @@ -202,7 +210,7 @@ if ( !array_key_exists( $ns, $namespaces ) ) { $namespaces[$ns] = false; } - } elseif ( $ns === NS_CONFIG ) { + } elseif ( $ns === NS_CONFIG ) { $warnFunc( "JsonConfig: Parameter 'nsName' in \$wgJsonConfigs['$confId'] is not " . "supported for namespace == NS_CONFIG ($ns)" ); } else { @@ -213,7 +221,7 @@ "if given, nsName must be a string" ); continue; } elseif ( array_key_exists( $ns, $namespaces ) && - $namespaces[$ns] !== null + $namespaces[$ns] !== null ) { if ( $namespaces[$ns] !== $nsName || $namespaces[$ns + 1] !== $nsTalk @@ -458,30 +466,39 @@ $cache = self::$titleMapCacheLru[$namespace]; } + if ( !self::$language ) { + self::$language = Language::factory( 'en' ); + } + // Parse string if needed // TODO: should the string parsing also be cached? if ( is_string( $value ) ) { if ( !self::$titleParser ) { self::$titleParser = - new MediaWikiTitleCodec( Language::factory( 'en' ), new GenderCache() ); + new MediaWikiTitleCodec( + self::$language, + new GenderCache(), + [], + new FauxInterwikiLookup() ); } - // Major hack, but until MediaWikiTitleCodec has global state, I can't think of a - // better way. Interwiki prefixes are a special case for title parsing: + // Interwiki prefixes are a special case for title parsing: // first letter is not capitalized, namespaces are not resolved, etc. // So we prepend an interwiki prefix to fool title codec, and later remove it. - global $wgJsonConfigInterwikiPrefix; try { - $value = $wgJsonConfigInterwikiPrefix . ':' . $value; + $value = FauxInterwikiLookup::MagicInterwikiPrefix . ':' . $value; $parts = self::$titleParser->splitTitleString( $value ); + + // Defensive coding - ensure the parsing has proceeded as expected if ( $parts['dbkey'] === '' || $parts['namespace'] !== 0 || $parts['fragment'] !== '' || $parts['local_interwiki'] !== false || - $parts['interwiki'] !== $wgJsonConfigInterwikiPrefix + $parts['interwiki'] !== FauxInterwikiLookup::MagicInterwikiPrefix ) { return null; } } catch ( MalformedTitleException $e ) { return null; } + $dbKey = $parts['dbkey']; } else { $dbKey = $value->getDBkey(); @@ -497,7 +514,8 @@ foreach ( $map[$namespace] as $conf ) { $re = $conf->pattern; if ( !$re || preg_match( $re, $dbKey ) ) { - $result = new JCTitle( $namespace, $dbKey, $conf ); + $dbKey2 = $conf->capitalize ? self::$language->ucfirst( $dbKey ) : $dbKey; + $result = new JCTitle( $namespace, $dbKey2, $conf ); break; } } @@ -540,16 +558,16 @@ if ( $name === false ) { // must be already declared if ( !array_key_exists( $ns, $namespaces ) ) { wfLogWarning( "JsonConfig: Invalid \$wgJsonConfigs: Namespace $ns " . - "has not been declared by core or other extensions" ); + "has not been declared by core or other extensions" ); } } elseif ( array_key_exists( $ns, $namespaces ) ) { wfLogWarning( "JsonConfig: Invalid \$wgJsonConfigs: Namespace $ns => '$name' " . - "is already declared as '$namespaces[$ns]'" ); + "is already declared as '$namespaces[$ns]'" ); } else { $key = array_search( $name, $namespaces ); if ( $key !== false ) { wfLogWarning( "JsonConfig: Invalid \$wgJsonConfigs: Namespace $ns => '$name' " . - "has identical name with the namespace #$key" ); + "has identical name with the namespace #$key" ); } else { $namespaces[$ns] = $name; } @@ -757,7 +775,7 @@ // todo/fixme? We should probably add ext.jsonConfig style to only those pages that pass parseTitle() $handler = ContentHandler::getForModelID( $title->getContentModel() ); if ( $handler->getDefaultFormat() === CONTENT_FORMAT_JSON || - self::parseTitle( $title ) + self::parseTitle( $title ) ) { $out->addModuleStyles( 'ext.jsonConfig' ); } @@ -816,7 +834,7 @@ } public static function onPageContentSaveComplete( /** @noinspection PhpUnusedParameterInspection */ $article, $user, $content, $summary, $isMinor, $isWatch, - $section, $flags, $revision, $status, $baseRevId ) { + $section, $flags, $revision, $status, $baseRevId ) { return self::onArticleChangeComplete( $article, $content ); } @@ -830,7 +848,7 @@ public static function onTitleMoveComplete( /** @noinspection PhpUnusedParameterInspection */ $title, $newTitle, $wgUser, $pageid, $redirid, $reason ) { return self::onArticleChangeComplete( $title ) || - self::onArticleChangeComplete( $newTitle ); + self::onArticleChangeComplete( $newTitle ); } /** @@ -920,3 +938,50 @@ return $isStorage; } } + +class FauxInterwikiLookup implements InterwikiLookup { + + /** + * This class will only accept this string as a valid interwiki + */ + const MagicInterwikiPrefix = 'Xyz'; + + /** + * Check whether an interwiki prefix exists + * + * @param string $prefix Interwiki prefix to use + * @return bool Whether it exists + */ + public function isValidInterwiki( $prefix ) { + return (bool) $this->fetch( $prefix ); + } + + /** + * Fetch an Interwiki object + * + * @param string $prefix Interwiki prefix to use + * @return Interwiki|null|bool + */ + public function fetch( $prefix ) { + if ( $prefix !== self::MagicInterwikiPrefix ) { + return false; + } + return new Interwiki( self::MagicInterwikiPrefix ); + } + + /** + * Returns all interwiki prefixes + * + * @param string|null $local If set, limits output to local/non-local interwikis + * @return string[] List of prefixes + */ + public function getAllPrefixes( $local = null ) { + return ( $local === null || $local === false ) ? [ self::MagicInterwikiPrefix ] : [ ]; + } + + /** + * Purge the in-process and persistent object cache for an interwiki prefix + * @param string $prefix + */ + public function invalidateCache( $prefix ) {} +} diff --git a/tests/phpunit/JCTitleParsingTest.php b/tests/phpunit/JCTitleParsingTest.php index d329854..050f4d3 100644 --- a/tests/phpunit/JCTitleParsingTest.php +++ b/tests/phpunit/JCTitleParsingTest.php @@ -24,24 +24,6 @@ 'wgMetaNamespace' => 'Project', 'wgLocalInterwikis' => [ 'localtestiw' ], 'wgCapitalLinks' => true, - - // Make sure we use the matching prefix - 'wgJsonConfigInterwikiPrefix' => 'remotetestiw', - - // NOTE: this is why global state is evil. - // TODO: refactor access to the interwiki codes so it can be injected. - 'wgHooks' => [ - 'InterwikiLoadPrefix' => [ - function ( $prefix, &$data ) { - if ( $prefix === 'localtestiw' ) { - $data = [ 'iw_url' => 'localtestiw' ]; - } elseif ( $prefix === 'remotetestiw' ) { - $data = [ 'iw_url' => 'remotetestiw' ]; - } - return false; - } - ] - ] ] ); $this->setUserLang( 'en' ); $this->setContentLang( 'en' ); -- To view, visit https://gerrit.wikimedia.org/r/332089 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I72ca4c5ca36b7e04f1f002ad4df74dff3b2600b8 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/JsonConfig Gerrit-Branch: master Gerrit-Owner: Yurik <yu...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits