Merge "i18n: Provide new "publish" strings for extra translator time"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 26 Apr 2016 19:02:26 +0000 (19:02 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 26 Apr 2016 19:02:26 +0000 (19:02 +0000)
36 files changed:
autoload.php
includes/DefaultSettings.php
includes/EventRelayerGroup.php
includes/MediaWikiServices.php
includes/ServiceWiring.php
includes/WatchedItemStore.php
includes/api/ApiOpenSearch.php
includes/api/ApiQueryPrefixSearch.php
includes/api/ApiQuerySearch.php
includes/debug/MWDebug.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/SearchUpdate.php
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/HTMLFormFieldCloner.php
includes/libs/objectcache/WANObjectCache.php
includes/objectcache/ObjectCache.php
includes/search/SearchEngine.php
includes/search/SearchEngineConfig.php [new file with mode: 0644]
includes/search/SearchEngineFactory.php [new file with mode: 0644]
includes/search/SearchNearMatchResultSet.php
includes/search/SearchNearMatcher.php [new file with mode: 0644]
includes/search/SearchResult.php
includes/specialpage/SpecialPage.php
includes/specials/SpecialFileDuplicateSearch.php
includes/specials/SpecialSearch.php
includes/specials/SpecialUpload.php
includes/user/User.php
languages/i18n/en.json
maintenance/updateCollation.php
resources/src/mediawiki/ForeignApi.js
resources/src/mediawiki/api.js
tests/phpunit/includes/MediaWikiServicesTest.php
tests/phpunit/includes/deferred/SearchUpdateTest.php
tests/phpunit/includes/search/SearchEnginePrefixTest.php
tests/phpunit/includes/specials/SpecialSearchTest.php

index 9b155bb..f802ddd 100644 (file)
@@ -1135,12 +1135,15 @@ $wgAutoloadLocalClasses = [
        'SearchDatabase' => __DIR__ . '/includes/search/SearchDatabase.php',
        'SearchDump' => __DIR__ . '/maintenance/dumpIterator.php',
        'SearchEngine' => __DIR__ . '/includes/search/SearchEngine.php',
+       'SearchEngineConfig' => __DIR__ . '/includes/search/SearchEngineConfig.php',
        'SearchEngineDummy' => __DIR__ . '/includes/search/SearchEngine.php',
+       'SearchEngineFactory' => __DIR__ . '/includes/search/SearchEngineFactory.php',
        'SearchExactMatchRescorer' => __DIR__ . '/includes/search/SearchExactMatchRescorer.php',
        'SearchHighlighter' => __DIR__ . '/includes/search/SearchHighlighter.php',
        'SearchMssql' => __DIR__ . '/includes/search/SearchMssql.php',
        'SearchMySQL' => __DIR__ . '/includes/search/SearchMySQL.php',
        'SearchNearMatchResultSet' => __DIR__ . '/includes/search/SearchNearMatchResultSet.php',
+       'SearchNearMatcher' => __DIR__ . '/includes/search/SearchNearMatcher.php',
        'SearchOracle' => __DIR__ . '/includes/search/SearchOracle.php',
        'SearchPostgres' => __DIR__ . '/includes/search/SearchPostgres.php',
        'SearchResult' => __DIR__ . '/includes/search/SearchResult.php',
index 7d771c1..ccfb10a 100644 (file)
@@ -8057,7 +8057,7 @@ $wgPopularPasswordFile = __DIR__ . '/../serialized/commonpasswords.cdb';
 $wgMaxUserDBWriteDuration = false;
 
 /**
- * Mapping of event channels to EventRelayer configuration.
+ * Mapping of event channels (or channel categories) to EventRelayer configuration.
  *
  * By setting up a PubSub system (like Kafka) and enabling a corresponding EventRelayer class
  * that uses it, MediaWiki can broadcast events to all subscribers. Certain features like WAN
@@ -8065,7 +8065,13 @@ $wgMaxUserDBWriteDuration = false;
  * subscribe to the channel and take actions based on the events. For example, a local daemon
  * can run on each CDN cache node and perfom local purges based on the URL purge channel events.
  *
- * The 'default' channel is for all channels without an explicit entry here.
+ * Some extensions may want to use "channel categories" so that different channels can also share
+ * the same custom relayer instance (e.g. when it's likely to be overriden). They can use
+ * EventRelayerGroup::getRelayer() based on the category but call notify() on various different
+ * actual channels. One reason for this would be that some system have very different performance
+ * vs durability needs, so one system (e.g. Kafka) may not be suitable for all uses.
+ *
+ * The 'default' key is for all channels (or channel categories) without an explicit entry here.
  *
  * @since 1.27
  */
index 9dfac79..9360693 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * Factory class for spawning EventRelayer objects using configuration
  *
@@ -12,25 +14,19 @@ class EventRelayerGroup {
        /** @var EventRelayer[] */
        protected $relayers = [];
 
-       /** @var EventRelayerGroup */
-       protected static $instance = null;
-
        /**
-        * @param Config $config
+        * @param array[] $config Channel configuration
         */
-       protected function __construct( Config $config ) {
-               $this->configByChannel = $config->get( 'EventRelayerConfig' );
+       public function __construct( array $config ) {
+               $this->configByChannel = $config;
        }
 
        /**
+        * @deprecated since 1.27 Use MediaWikiServices::getInstance()->getEventRelayerGroup()
         * @return EventRelayerGroup
         */
        public static function singleton() {
-               if ( !self::$instance ) {
-                       self::$instance = new self( RequestContext::getMain()->getConfig() );
-               }
-
-               return self::$instance;
+               return MediaWikiServices::getInstance()->getEventRelayerGroup();
        }
 
        /**
index 3f4d8ed..f0fe6e9 100644 (file)
@@ -2,6 +2,7 @@
 namespace MediaWiki;
 
 use ConfigFactory;
+use EventRelayerGroup;
 use GlobalVarConfig;
 use Config;
 use Hooks;
@@ -152,6 +153,35 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'StatsdDataFactory' );
        }
 
+       /**
+        * @return EventRelayerGroup
+        */
+       public function getEventRelayerGroup() {
+               return $this->getService( 'EventRelayerGroup' );
+       }
+
+       /**
+        * @return SearchEngine
+        */
+       public function newSearchEngine() {
+               // New engine object every time, since they keep state
+               return $this->getService( 'SearchEngineFactory' )->create();
+       }
+
+       /**
+        * @return SearchEngineFactory
+        */
+       public function getSearchEngineFactory() {
+               return $this->getService( 'SearchEngineFactory' );
+       }
+
+       /**
+        * @return SearchEngineConfig
+        */
+       public function getSearchEngineConfig() {
+               return $this->getService( 'SearchEngineConfig' );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service getter here, don't forget to add a test
        // case for it in MediaWikiServicesTest::provideGetters() and in
index 7e1d4e3..defe698 100644 (file)
@@ -78,6 +78,21 @@ return [
                );
        },
 
+       'EventRelayerGroup' => function( MediaWikiServices $services ) {
+               return new EventRelayerGroup( $services->getMainConfig()->get( 'EventRelayerConfig' ) );
+       },
+
+       'SearchEngineFactory' => function( MediaWikiServices $services ) {
+               // Create search engine
+               return new SearchEngineFactory( $services->getService( 'SearchEngineConfig' ) );
+       },
+
+       'SearchEngineConfig' => function( MediaWikiServices $services ) {
+               // Create a search engine config from main config.
+               $config = $services->getService( 'MainConfig' );
+               return new SearchEngineConfig( $config );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service here, don't forget to add a getter function
        // in the MediaWikiServices class. The convenience getter should just call
index 2180b2d..fcf6d3b 100644 (file)
@@ -621,7 +621,7 @@ class WatchedItemStore implements StatsdAwareInterface {
                $this->reuseConnection( $dbr );
 
                foreach ( $res as $row ) {
-                       $timestamps[(int)$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
+                       $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp;
                }
 
                return $timestamps;
index effa520..058e0a3 100644 (file)
@@ -24,6 +24,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @ingroup API
  */
@@ -123,8 +125,7 @@ class ApiOpenSearch extends ApiBase {
         * @param array &$results Put results here. Keys have to be integers.
         */
        protected function search( $search, $limit, $namespaces, $resolveRedir, &$results ) {
-
-               $searchEngine = SearchEngine::create();
+               $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
                $searchEngine->setLimitOffset( $limit );
                $searchEngine->setNamespaces( $namespaces );
                $titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) );
@@ -350,24 +351,25 @@ class ApiOpenSearch extends ApiBase {
         * @throws MWException
         */
        public static function getOpenSearchTemplate( $type ) {
-               global $wgOpenSearchTemplate, $wgCanonicalServer;
+               $config = MediaWikiServices::getInstance()->getSearchEngineConfig();
+               $template = $config->getConfig()->get( 'OpenSearchTemplate' );
 
-               if ( $wgOpenSearchTemplate && $type === 'application/x-suggestions+json' ) {
-                       return $wgOpenSearchTemplate;
+               if ( $template && $type === 'application/x-suggestions+json' ) {
+                       return $template;
                }
 
-               $ns = implode( '|', SearchEngine::defaultNamespaces() );
+               $ns = implode( '|', $config->defaultNamespaces() );
                if ( !$ns ) {
                        $ns = '0';
                }
 
                switch ( $type ) {
                        case 'application/x-suggestions+json':
-                               return $wgCanonicalServer . wfScript( 'api' )
+                               return $config->getConfig()->get( 'CanonicalServer' ) . wfScript( 'api' )
                                        . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
 
                        case 'application/x-suggestions+xml':
-                               return $wgCanonicalServer . wfScript( 'api' )
+                               return $config->getConfig()->get( 'CanonicalServer' ) . wfScript( 'api' )
                                        . '?action=opensearch&format=xml&search={searchTerms}&namespace=' . $ns;
 
                        default:
index d04796c..5c50273 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * 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
@@ -45,7 +47,7 @@ class ApiQueryPrefixSearch extends ApiQueryGeneratorBase {
                $namespaces = $params['namespace'];
                $offset = $params['offset'];
 
-               $searchEngine = SearchEngine::create();
+               $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
                $searchEngine->setLimitOffset( $limit + 1, $offset );
                $searchEngine->setNamespaces( $namespaces );
                $titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) );
index 3955cc5..f57d3a3 100644 (file)
@@ -24,6 +24,8 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Query module to perform full text search within wiki titles and content
  *
@@ -78,8 +80,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                }
 
                // Create search engine instance and set options
-               $search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ?
-                       SearchEngine::create( $params['backend'] ) : SearchEngine::create();
+               $type = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ?
+                       $params['backend'] : null;
+               $search = MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type );
                $search->setLimitOffset( $limit + 1, $params['offset'] );
                $search->setNamespaces( $params['namespace'] );
                $search->setFeatureData( 'rewrite', (bool)$params['enablerewrites'] );
@@ -95,7 +98,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                } elseif ( $what == 'nearmatch' ) {
                        // near matches must receive the user input as provided, otherwise
                        // the near matches within namespaces are lost.
-                       $matches = SearchEngine::getNearMatchResultSet( $params['search'] );
+                       $matches = $search->getNearMatcher( $this->getConfig() )
+                               ->getNearMatchResultSet( $params['search'] );
                } else {
                        // We default to title searches; this is a terrible legacy
                        // of the way we initially set up the MySQL fulltext-based
@@ -358,13 +362,14 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
                        'enablerewrites' => false,
                ];
 
-               $alternatives = SearchEngine::getSearchTypes();
+               $searchConfig = MediaWikiServices::getInstance()->getSearchEngineConfig();
+               $alternatives = $searchConfig->getSearchTypes();
                if ( count( $alternatives ) > 1 ) {
                        if ( $alternatives[0] === null ) {
                                $alternatives[0] = self::BACKEND_NULL_PARAM;
                        }
                        $params['backend'] = [
-                               ApiBase::PARAM_DFLT => $this->getConfig()->get( 'SearchType' ),
+                               ApiBase::PARAM_DFLT => $searchConfig->getSearchType(),
                                ApiBase::PARAM_TYPE => $alternatives,
                        ];
                }
index 81770d5..13d25a8 100644 (file)
@@ -368,6 +368,9 @@ class MWDebug {
                        $sql
                );
 
+               // last check for invalid utf8
+               $sql = UtfNormal\Validator::cleanUp( $sql );
+
                self::$query[] = [
                        'sql' => $sql,
                        'function' => $function,
index 32f6adc..65ff9f3 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use Wikimedia\Assert\Assert;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Handles purging appropriate CDN URLs given a title (or titles)
@@ -109,7 +110,8 @@ class CdnCacheUpdate implements DeferrableUpdate, MergeableUpdate {
                wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) );
 
                // Reliably broadcast the purge to all edge nodes
-               $relayer = EventRelayerGroup::singleton()->getRelayer( 'cdn-url-purges' );
+               $relayer = MediaWikiServices::getInstance()->getEventRelayerGroup()
+                                       ->getRelayer( 'cdn-url-purges' );
                $relayer->notify(
                        'cdn-url-purges',
                        [
index 2abf028..62c8b00 100644 (file)
@@ -23,6 +23,8 @@
  * @ingroup Search
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Database independant search index updater
  *
@@ -75,14 +77,15 @@ class SearchUpdate implements DeferrableUpdate {
         * Perform actual update for the entry
         */
        public function doUpdate() {
-               global $wgDisableSearchUpdate;
+               $config = MediaWikiServices::getInstance()->getSearchEngineConfig();
 
-               if ( $wgDisableSearchUpdate || !$this->id ) {
+               if ( $config->getConfig()->get( 'DisableSearchUpdate' ) || !$this->id ) {
                        return;
                }
 
-               foreach ( SearchEngine::getSearchTypes() as $type ) {
-                       $search = SearchEngine::create( $type );
+               $seFactory = MediaWikiServices::getInstance()->getSearchEngineFactory();
+               foreach ( $config->getSearchTypes() as $type ) {
+                       $search = $seFactory->create( $type );
                        if ( !$search->supports( 'search-update' ) ) {
                                continue;
                        }
@@ -99,7 +102,7 @@ class SearchUpdate implements DeferrableUpdate {
 
                        $text = $search->getTextFromContent( $this->title, $this->content );
                        if ( !$search->textAlreadyUpdatedForIndex() ) {
-                               $text = self::updateText( $text );
+                               $text = $this->updateText( $text, $search );
                        }
 
                        # Perform the actual update
@@ -113,14 +116,16 @@ class SearchUpdate implements DeferrableUpdate {
         * If you're using a real search engine, you'll probably want to override
         * this behavior and do something nicer with the original wikitext.
         * @param string $text
+        * @param SearchEngine $se Search engine
         * @return string
         */
-       public static function updateText( $text ) {
+       public function updateText( $text, SearchEngine $se = null ) {
                global $wgContLang;
 
                # Language-specific strip/conversion
                $text = $wgContLang->normalizeForSearch( $text );
-               $lc = SearchEngine::legalSearchChars() . '&#;';
+               $se = $se ?: MediaWikiServices::getInstance()->newSearchEngine();
+               $lc = $se->legalSearchChars() . '&#;';
 
                $text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
                        ' ', $wgContLang->lc( " " . $text . " " ) ); # Strip HTML markup
index 6f88975..8f8caf2 100644 (file)
@@ -1493,7 +1493,7 @@ class HTMLForm extends ContextSource {
 
                foreach ( $fields as $key => $value ) {
                        if ( $value instanceof HTMLFormField ) {
-                               $v = isset( $this->mFieldData[$key] )
+                               $v = array_key_exists( $key, $this->mFieldData )
                                        ? $this->mFieldData[$key]
                                        : $value->getDefault();
 
index d5f4cc0..d14fa90 100644 (file)
@@ -117,8 +117,8 @@ abstract class HTMLFormField {
                        $tmp = $m[1];
                }
                if ( substr( $tmp, 0, 2 ) == 'wp' &&
-                       !isset( $alldata[$tmp] ) &&
-                       isset( $alldata[substr( $tmp, 2 )] )
+                       !array_key_exists( $tmp, $alldata ) &&
+                       array_key_exists( substr( $tmp, 2 ), $alldata )
                ) {
                        // Adjust for name mangling.
                        $tmp = substr( $tmp, 2 );
@@ -139,7 +139,7 @@ abstract class HTMLFormField {
                        $data = $alldata;
                        while ( $keys ) {
                                $key = array_shift( $keys );
-                               if ( !is_array( $data ) || !isset( $data[$key] ) ) {
+                               if ( !is_array( $data ) || !array_key_exists( $key, $data ) ) {
                                        continue 2;
                                }
                                $data = $data[$key];
index 7359092..3f80884 100644 (file)
@@ -271,7 +271,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
 
                $fields = $this->createFieldsForKey( $key );
                foreach ( $fields as $fieldname => $field ) {
-                       $v = isset( $values[$fieldname] )
+                       $v = array_key_exists( $fieldname, $values )
                                ? $values[$fieldname]
                                : $field->getDefault();
 
index 57cecd0..18cc10e 100644 (file)
@@ -675,12 +675,12 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *
         *             return CatConfig::newFromRow( $dbr->selectRow( ... ) );
         *         },
-        *         array(
+        *         [
         *             // Calling touchCheckKey() on this key invalidates the cache
-        *             'checkKeys' => array( $cache->makeKey( 'site-cat-config' ) ),
+        *             'checkKeys' => [ $cache->makeKey( 'site-cat-config' ) ],
         *             // Try to only let one datacenter thread manage cache updates at a time
         *             'lockTSE' => 30
-        *         )
+        *         ]
         *     );
         * @endcode
         *
@@ -700,15 +700,15 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *
         *             return CatState::newFromResults( $dbr->select( ... ) );
         *         },
-        *         array(
+        *         [
         *              // The "check" keys that represent things the value depends on;
         *              // Calling touchCheckKey() on any of them invalidates the cache
-        *             'checkKeys' => array(
+        *             'checkKeys' => [
         *                 $cache->makeKey( 'sustenance-bowls', $cat->getRoomId() ),
         *                 $cache->makeKey( 'people-present', $cat->getHouseId() ),
         *                 $cache->makeKey( 'cat-laws', $cat->getCityId() ),
-        *             )
-        *         )
+        *             ]
+        *         ]
         *     );
         * @endcode
         *
@@ -726,7 +726,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *             $setOpts += Database::getCacheSetOptions( $dbr );
         *
         *             // Start off with the last cached list
-        *             $list = $oldValue ?: array();
+        *             $list = $oldValue ?: [];
         *             // Fetch the last 100 relevant rows in descending order;
         *             // only fetch rows newer than $list[0] to reduce scanning
         *             $rows = iterator_to_array( $dbr->select( ... ) );
@@ -734,7 +734,7 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *             return array_slice( array_merge( $new, $list ), 0, 100 );
         *        },
         *        // Try to only let one datacenter thread manage cache updates at a time
-        *        array( 'lockTSE' => 30 )
+        *        [ 'lockTSE' => 30 ]
         *     );
         * @endcode
         *
index e9f2211..24846e6 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Functions to get cache objects
@@ -299,7 +300,8 @@ class ObjectCache {
 
                $params = $wgWANObjectCaches[$id];
                foreach ( $params['channels'] as $action => $channel ) {
-                       $params['relayers'][$action] = EventRelayerGroup::singleton()->getRelayer( $channel );
+                       $params['relayers'][$action] = MediaWikiServices::getInstance()->getEventRelayerGroup()
+                               ->getRelayer( $channel );
                        $params['channels'][$action] = $channel;
                }
                $params['cache'] = self::newFromId( $params['cacheId'] );
index b263fb3..3d2057c 100644 (file)
  * @defgroup Search Search
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Contain a class for special pages
  * @ingroup Search
  */
-class SearchEngine {
+abstract class SearchEngine {
        /** @var string */
        public $prefix = '';
 
@@ -124,155 +126,55 @@ class SearchEngine {
         * @param string $term
         * @return string
         */
-       function transformSearchTerm( $term ) {
+       public function transformSearchTerm( $term ) {
                return $term;
        }
 
+       /**
+        * Get service class to finding near matches.
+        * @param Config $config Configuration to use for the matcher.
+        * @return SearchNearMatcher
+        */
+       public function getNearMatcher( Config $config ) {
+               return new SearchNearMatcher( $config );
+       }
+
+       /**
+        * Get near matcher for default SearchEngine.
+        * @return SearchNearMatcher
+        */
+       protected static function defaultNearMatcher() {
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+               return MediaWikiServices::getInstance()->newSearchEngine()->getNearMatcher( $config );
+       }
+
        /**
         * If an exact title match can be found, or a very slightly close match,
         * return the title. If no match, returns NULL.
-        *
+        * @deprecated since 1.27; Use SearchEngine::getNearMatcher()
         * @param string $searchterm
         * @return Title
         */
        public static function getNearMatch( $searchterm ) {
-               $title = self::getNearMatchInternal( $searchterm );
-
-               Hooks::run( 'SearchGetNearMatchComplete', [ $searchterm, &$title ] );
-               return $title;
+               return static::defaultNearMatcher()->getNearMatch( $searchterm );
        }
 
        /**
         * Do a near match (see SearchEngine::getNearMatch) and wrap it into a
         * SearchResultSet.
-        *
+        * @deprecated since 1.27; Use SearchEngine::getNearMatcher()
         * @param string $searchterm
         * @return SearchResultSet
         */
        public static function getNearMatchResultSet( $searchterm ) {
-               return new SearchNearMatchResultSet( self::getNearMatch( $searchterm ) );
+               return static::defaultNearMatcher()->getNearMatchResultSet( $searchterm );
        }
 
        /**
-        * Really find the title match.
-        * @param string $searchterm
-        * @return null|Title
+        * Get chars legal for search.
+        * NOTE: usage as static is deprecated and preserved only as BC measure
+        * @return string
         */
-       private static function getNearMatchInternal( $searchterm ) {
-               global $wgContLang, $wgEnableSearchContributorsByIP;
-
-               $allSearchTerms = [ $searchterm ];
-
-               if ( $wgContLang->hasVariants() ) {
-                       $allSearchTerms = array_unique( array_merge(
-                               $allSearchTerms,
-                               $wgContLang->autoConvertToAllVariants( $searchterm )
-                       ) );
-               }
-
-               $titleResult = null;
-               if ( !Hooks::run( 'SearchGetNearMatchBefore', [ $allSearchTerms, &$titleResult ] ) ) {
-                       return $titleResult;
-               }
-
-               foreach ( $allSearchTerms as $term ) {
-
-                       # Exact match? No need to look further.
-                       $title = Title::newFromText( $term );
-                       if ( is_null( $title ) ) {
-                               return null;
-                       }
-
-                       # Try files if searching in the Media: namespace
-                       if ( $title->getNamespace() == NS_MEDIA ) {
-                               $title = Title::makeTitle( NS_FILE, $title->getText() );
-                       }
-
-                       if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
-                               return $title;
-                       }
-
-                       # See if it still otherwise has content is some sane sense
-                       $page = WikiPage::factory( $title );
-                       if ( $page->hasViewableContent() ) {
-                               return $title;
-                       }
-
-                       if ( !Hooks::run( 'SearchAfterNoDirectMatch', [ $term, &$title ] ) ) {
-                               return $title;
-                       }
-
-                       # Now try all lower case (i.e. first letter capitalized)
-                       $title = Title::newFromText( $wgContLang->lc( $term ) );
-                       if ( $title && $title->exists() ) {
-                               return $title;
-                       }
-
-                       # Now try capitalized string
-                       $title = Title::newFromText( $wgContLang->ucwords( $term ) );
-                       if ( $title && $title->exists() ) {
-                               return $title;
-                       }
-
-                       # Now try all upper case
-                       $title = Title::newFromText( $wgContLang->uc( $term ) );
-                       if ( $title && $title->exists() ) {
-                               return $title;
-                       }
-
-                       # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
-                       $title = Title::newFromText( $wgContLang->ucwordbreaks( $term ) );
-                       if ( $title && $title->exists() ) {
-                               return $title;
-                       }
-
-                       // Give hooks a chance at better match variants
-                       $title = null;
-                       if ( !Hooks::run( 'SearchGetNearMatch', [ $term, &$title ] ) ) {
-                               return $title;
-                       }
-               }
-
-               $title = Title::newFromText( $searchterm );
-
-               # Entering an IP address goes to the contributions page
-               if ( $wgEnableSearchContributorsByIP ) {
-                       if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
-                               || User::isIP( trim( $searchterm ) ) ) {
-                               return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
-                       }
-               }
-
-               # Entering a user goes to the user page whether it's there or not
-               if ( $title->getNamespace() == NS_USER ) {
-                       return $title;
-               }
-
-               # Go to images that exist even if there's no local page.
-               # There may have been a funny upload, or it may be on a shared
-               # file repository such as Wikimedia Commons.
-               if ( $title->getNamespace() == NS_FILE ) {
-                       $image = wfFindFile( $title );
-                       if ( $image ) {
-                               return $title;
-                       }
-               }
-
-               # MediaWiki namespace? Page may be "implied" if not customized.
-               # Just return it, with caps forced as the message system likes it.
-               if ( $title->getNamespace() == NS_MEDIAWIKI ) {
-                       return Title::makeTitle( NS_MEDIAWIKI, $wgContLang->ucfirst( $title->getText() ) );
-               }
-
-               # Quoted term? Try without the quotes...
-               $matches = [];
-               if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
-                       return SearchEngine::getNearMatch( $matches[1] );
-               }
-
-               return null;
-       }
-
        public static function legalSearchChars() {
                return "A-Za-z_'.0-9\\x80-\\xFF\\-";
        }
@@ -390,44 +292,8 @@ class SearchEngine {
                return $parsed;
        }
 
-       /**
-        * Make a list of searchable namespaces and their canonical names.
-        * @return array
-        */
-       public static function searchableNamespaces() {
-               global $wgContLang;
-               $arr = [];
-               foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
-                       if ( $ns >= NS_MAIN ) {
-                               $arr[$ns] = $name;
-                       }
-               }
-
-               Hooks::run( 'SearchableNamespaces', [ &$arr ] );
-               return $arr;
-       }
-
-       /**
-        * Extract default namespaces to search from the given user's
-        * settings, returning a list of index numbers.
-        *
-        * @param user $user
-        * @return array
-        */
-       public static function userNamespaces( $user ) {
-               $arr = [];
-               foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
-                       if ( $user->getOption( 'searchNs' . $ns ) ) {
-                               $arr[] = $ns;
-                       }
-               }
-
-               return $arr;
-       }
-
        /**
         * Find snippet highlight settings for all users
-        *
         * @return array Contextlines, contextchars
         */
        public static function userHighlightPrefs() {
@@ -436,77 +302,6 @@ class SearchEngine {
                return [ $contextlines, $contextchars ];
        }
 
-       /**
-        * An array of namespaces indexes to be searched by default
-        *
-        * @return array
-        */
-       public static function defaultNamespaces() {
-               global $wgNamespacesToBeSearchedDefault;
-
-               return array_keys( $wgNamespacesToBeSearchedDefault, true );
-       }
-
-       /**
-        * Get a list of namespace names useful for showing in tooltips
-        * and preferences
-        *
-        * @param array $namespaces
-        * @return array
-        */
-       public static function namespacesAsText( $namespaces ) {
-               global $wgContLang;
-
-               $formatted = array_map( [ $wgContLang, 'getFormattedNsText' ], $namespaces );
-               foreach ( $formatted as $key => $ns ) {
-                       if ( empty( $ns ) ) {
-                               $formatted[$key] = wfMessage( 'blanknamespace' )->text();
-                       }
-               }
-               return $formatted;
-       }
-
-       /**
-        * Load up the appropriate search engine class for the currently
-        * active database backend, and return a configured instance.
-        *
-        * @param string $type Type of search backend, if not the default
-        * @return SearchEngine
-        */
-       public static function create( $type = null ) {
-               global $wgSearchType;
-               $dbr = null;
-
-               $alternatives = self::getSearchTypes();
-
-               if ( $type && in_array( $type, $alternatives ) ) {
-                       $class = $type;
-               } elseif ( $wgSearchType !== null ) {
-                       $class = $wgSearchType;
-               } else {
-                       $dbr = wfGetDB( DB_SLAVE );
-                       $class = $dbr->getSearchEngine();
-               }
-
-               $search = new $class( $dbr );
-               return $search;
-       }
-
-       /**
-        * Return the search engines we support. If only $wgSearchType
-        * is set, it'll be an array of just that one item.
-        *
-        * @return array
-        */
-       public static function getSearchTypes() {
-               global $wgSearchType, $wgSearchTypeAlternatives;
-
-               $alternatives = $wgSearchTypeAlternatives ?: [];
-               array_unshift( $alternatives, $wgSearchType );
-
-               return $alternatives;
-       }
-
        /**
         * Create or update the search index record for the given page.
         * Title and text should be pre-processed.
@@ -774,6 +569,67 @@ class SearchEngine {
                return $backend->defaultSearchBackend( $this->namespaces, $search, $this->limit, $this->offset );
        }
 
+       /**
+        * Make a list of searchable namespaces and their canonical names.
+        * @deprecated since 1.27; use SearchEngineConfig::searchableNamespaces()
+        * @return array
+        */
+       public static function searchableNamespaces() {
+               return MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
+       }
+
+       /**
+        * Extract default namespaces to search from the given user's
+        * settings, returning a list of index numbers.
+        * @deprecated since 1.27; use SearchEngineConfig::userNamespaces()
+        * @param user $user
+        * @return array
+        */
+       public static function userNamespaces( $user ) {
+               return MediaWikiServices::getInstance()->getSearchEngineConfig()->userNamespaces( $user );
+       }
+
+       /**
+        * An array of namespaces indexes to be searched by default
+        * @deprecated since 1.27; use SearchEngineConfig::defaultNamespaces()
+        * @return array
+        */
+       public static function defaultNamespaces() {
+               return MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces();
+       }
+
+       /**
+        * Get a list of namespace names useful for showing in tooltips
+        * and preferences
+        * @deprecated since 1.27; use SearchEngineConfig::namespacesAsText()
+        * @param array $namespaces
+        * @return array
+        */
+       public static function namespacesAsText( $namespaces ) {
+               return MediaWikiServices::getInstance()->getSearchEngineConfig()->namespacesAsText();
+       }
+
+       /**
+        * Load up the appropriate search engine class for the currently
+        * active database backend, and return a configured instance.
+        * @deprecated since 1.27; Use SearchEngineFactory::create
+        * @param string $type Type of search backend, if not the default
+        * @return SearchEngine
+        */
+       public static function create( $type = null ) {
+               return MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $type );
+       }
+
+       /**
+        * Return the search engines we support. If only $wgSearchType
+        * is set, it'll be an array of just that one item.
+        * @deprecated since 1.27; use SearchEngineConfig::getSearchTypes()
+        * @return array
+        */
+       public static function getSearchTypes() {
+               return MediaWikiServices::getInstance()->getSearchEngineConfig()->getSearchTypes();
+       }
+
 }
 
 /**
diff --git a/includes/search/SearchEngineConfig.php b/includes/search/SearchEngineConfig.php
new file mode 100644 (file)
index 0000000..3d996ba
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * Configuration handling class for SearchEngine.
+ * Provides added service over plain configuration.
+ *
+ * @since 1.27
+ */
+class SearchEngineConfig {
+
+       /**
+        * Config object from which the settings will be derived.
+        * @var Config
+        */
+       private $config;
+
+       public function __construct( Config $config ) {
+               $this->config = $config;
+       }
+
+       /**
+        * Retrieve original config.
+        * @return Config
+        */
+       public function getConfig() {
+               return $this->config;
+       }
+
+       /**
+        * Make a list of searchable namespaces and their canonical names.
+        * @return array Namespace ID => name
+        */
+       public function searchableNamespaces() {
+               $arr = [];
+               foreach ( $this->config->get( 'ContLang' )->getNamespaces() as $ns => $name ) {
+                       if ( $ns >= NS_MAIN ) {
+                               $arr[$ns] = $name;
+                       }
+               }
+
+               Hooks::run( 'SearchableNamespaces', [ &$arr ] );
+               return $arr;
+       }
+
+       /**
+        * Extract default namespaces to search from the given user's
+        * settings, returning a list of index numbers.
+        *
+        * @param user $user
+        * @return int[]
+        */
+       public function userNamespaces( $user ) {
+               $arr = [];
+               foreach ( $this->searchableNamespaces() as $ns => $name ) {
+                       if ( $user->getOption( 'searchNs' . $ns ) ) {
+                               $arr[] = $ns;
+                       }
+               }
+
+               return $arr;
+       }
+
+       /**
+        * An array of namespaces indexes to be searched by default
+        *
+        * @return int[] Namespace IDs
+        */
+       public function defaultNamespaces() {
+               return array_keys( $this->config->get( 'NamespacesToBeSearchedDefault' ), true );
+       }
+
+       /**
+        * Return the search engines we support. If only $wgSearchType
+        * is set, it'll be an array of just that one item.
+        *
+        * @return array
+        */
+       public function getSearchTypes() {
+               $alternatives = $this->config->get( 'SearchTypeAlternatives' ) ?: [];
+               array_unshift( $alternatives, $this->config->get( 'SearchType' ) );
+
+               return $alternatives;
+       }
+
+       /**
+        * Return the search engine configured in $wgSearchType, etc.
+        *
+        * @return string|null
+        */
+       public function getSearchType() {
+               return $this->config->get( 'SearchType' );
+       }
+
+       /**
+        * Get a list of namespace names useful for showing in tooltips
+        * and preferences.
+        *
+        * @param int[] $namespaces
+        * @return string[] List of names
+        */
+       public function namespacesAsText( $namespaces ) {
+               $formatted = array_map( [ $this->config->get( 'ContLang' ), 'getFormattedNsText' ], $namespaces );
+               foreach ( $formatted as $key => $ns ) {
+                       if ( empty( $ns ) ) {
+                               $formatted[$key] = wfMessage( 'blanknamespace' )->text();
+                       }
+               }
+               return $formatted;
+       }
+}
diff --git a/includes/search/SearchEngineFactory.php b/includes/search/SearchEngineFactory.php
new file mode 100644 (file)
index 0000000..67f500c
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Factory class for SearchEngine.
+ * Allows to create engine of the specific type.
+ */
+class SearchEngineFactory {
+
+       /**
+        * Configuration for SearchEngine classes.
+        * @var SearchEngineConfig
+        */
+       private $config;
+
+       public function __construct( SearchEngineConfig $config ) {
+               $this->config = $config;
+       }
+
+       /**
+        * Create SearchEngine of the given type.
+        * @param string $type
+        * @return SearchEngine
+        */
+       public function create( $type = null ) {
+               $dbr = null;
+
+               $configType = $this->config->getSearchType();
+               $alternatives = $this->config->getSearchTypes();
+
+               if ( $type && in_array( $type, $alternatives ) ) {
+                       $class = $type;
+               } elseif ( $configType !== null ) {
+                       $class = $configType;
+               } else {
+                       $dbr = wfGetDB( DB_SLAVE );
+                       $class = $dbr->getSearchEngine();
+               }
+
+               $search = new $class( $dbr );
+               return $search;
+       }
+}
index 510726b..6d66707 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * A SearchResultSet wrapper for SearchEngine::getNearMatch
+ * A SearchResultSet wrapper for SearchNearMatcher
  */
 class SearchNearMatchResultSet extends SearchResultSet {
        private $fetched = false;
diff --git a/includes/search/SearchNearMatcher.php b/includes/search/SearchNearMatcher.php
new file mode 100644 (file)
index 0000000..bb7cd57
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+
+/**
+ * Implementation of near match title search.
+ * TODO: split into service/implementation.
+ */
+class SearchNearMatcher {
+       /**
+        * Configuration object.
+        * @param Config $config
+        */
+       protected $config;
+
+       public function __construct( Config $config ) {
+
+               $this->config = $config;
+       }
+
+       /**
+        * If an exact title match can be found, or a very slightly close match,
+        * return the title. If no match, returns NULL.
+        *
+        * @param string $searchterm
+        * @return Title
+        */
+       public function getNearMatch( $searchterm ) {
+               $title = $this->getNearMatchInternal( $searchterm );
+
+               Hooks::run( 'SearchGetNearMatchComplete', [ $searchterm, &$title ] );
+               return $title;
+       }
+
+       /**
+        * Do a near match (see SearchEngine::getNearMatch) and wrap it into a
+        * SearchResultSet.
+        *
+        * @param string $searchterm
+        * @return SearchResultSet
+        */
+       public function getNearMatchResultSet( $searchterm ) {
+               return new SearchNearMatchResultSet( $this->getNearMatch( $searchterm ) );
+       }
+
+       /**
+        * Really find the title match.
+        * @param string $searchterm
+        * @return null|Title
+        */
+       protected function getNearMatchInternal( $searchterm ) {
+               $lang = $this->config->get( 'ContLang' );
+
+               $allSearchTerms = [ $searchterm ];
+
+               if ( $lang->hasVariants() ) {
+                       $allSearchTerms = array_unique( array_merge(
+                               $allSearchTerms,
+                               $lang->autoConvertToAllVariants( $searchterm )
+                       ) );
+               }
+
+               $titleResult = null;
+               if ( !Hooks::run( 'SearchGetNearMatchBefore', [ $allSearchTerms, &$titleResult ] ) ) {
+                       return $titleResult;
+               }
+
+               foreach ( $allSearchTerms as $term ) {
+
+                       # Exact match? No need to look further.
+                       $title = Title::newFromText( $term );
+                       if ( is_null( $title ) ) {
+                               return null;
+                       }
+
+                       # Try files if searching in the Media: namespace
+                       if ( $title->getNamespace() == NS_MEDIA ) {
+                               $title = Title::makeTitle( NS_FILE, $title->getText() );
+                       }
+
+                       if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
+                               return $title;
+                       }
+
+                       # See if it still otherwise has content is some sane sense
+                       $page = WikiPage::factory( $title );
+                       if ( $page->hasViewableContent() ) {
+                               return $title;
+                       }
+
+                       if ( !Hooks::run( 'SearchAfterNoDirectMatch', [ $term, &$title ] ) ) {
+                               return $title;
+                       }
+
+                       # Now try all lower case (i.e. first letter capitalized)
+                       $title = Title::newFromText( $lang->lc( $term ) );
+                       if ( $title && $title->exists() ) {
+                               return $title;
+                       }
+
+                       # Now try capitalized string
+                       $title = Title::newFromText( $lang->ucwords( $term ) );
+                       if ( $title && $title->exists() ) {
+                               return $title;
+                       }
+
+                       # Now try all upper case
+                       $title = Title::newFromText( $lang->uc( $term ) );
+                       if ( $title && $title->exists() ) {
+                               return $title;
+                       }
+
+                       # Now try Word-Caps-Breaking-At-Word-Breaks, for hyphenated names etc
+                       $title = Title::newFromText( $lang->ucwordbreaks( $term ) );
+                       if ( $title && $title->exists() ) {
+                               return $title;
+                       }
+
+                       // Give hooks a chance at better match variants
+                       $title = null;
+                       if ( !Hooks::run( 'SearchGetNearMatch', [ $term, &$title ] ) ) {
+                               return $title;
+                       }
+               }
+
+               $title = Title::newFromText( $searchterm );
+
+               # Entering an IP address goes to the contributions page
+               if ( $this->config->get( 'EnableSearchContributorsByIP' ) ) {
+                       if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
+                               || User::isIP( trim( $searchterm ) ) ) {
+                               return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
+                       }
+               }
+
+               # Entering a user goes to the user page whether it's there or not
+               if ( $title->getNamespace() == NS_USER ) {
+                       return $title;
+               }
+
+               # Go to images that exist even if there's no local page.
+               # There may have been a funny upload, or it may be on a shared
+               # file repository such as Wikimedia Commons.
+               if ( $title->getNamespace() == NS_FILE ) {
+                       $image = wfFindFile( $title );
+                       if ( $image ) {
+                               return $title;
+                       }
+               }
+
+               # MediaWiki namespace? Page may be "implied" if not customized.
+               # Just return it, with caps forced as the message system likes it.
+               if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+                       return Title::makeTitle( NS_MEDIAWIKI, $lang->ucfirst( $title->getText() ) );
+               }
+
+               # Quoted term? Try without the quotes...
+               $matches = [];
+               if ( preg_match( '/^"([^"]+)"$/', $searchterm, $matches ) ) {
+                       return self::getNearMatch( $matches[1] );
+               }
+
+               return null;
+       }
+}
index 6c406e7..21effbb 100644 (file)
@@ -21,6 +21,8 @@
  * @ingroup Search
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * @todo FIXME: This class is horribly factored. It would probably be better to
  * have a useful base class to which you pass some standard information, then
@@ -49,6 +51,11 @@ class SearchResult {
         */
        protected $mText;
 
+       /**
+        * @var SearchEngine
+        */
+       protected $searchEngine;
+
        /**
         * Return a new SearchResult and initializes it with a title.
         *
@@ -56,7 +63,7 @@ class SearchResult {
         * @return SearchResult
         */
        public static function newFromTitle( $title ) {
-               $result = new self();
+               $result = new static();
                $result->initFromTitle( $title );
                return $result;
        }
@@ -78,6 +85,7 @@ class SearchResult {
                                $this->mImage = wfFindFile( $this->mTitle );
                        }
                }
+               $this->searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
        }
 
        /**
@@ -119,8 +127,8 @@ class SearchResult {
        protected function initText() {
                if ( !isset( $this->mText ) ) {
                        if ( $this->mRevision != null ) {
-                               $this->mText = SearchEngine::create()
-                                       ->getTextFromContent( $this->mTitle, $this->mRevision->getContent() );
+                               $this->mText = $this->searchEngine->getTextFromContent(
+                                               $this->mTitle, $this->mRevision->getContent() );
                        } else { // TODO: can we fetch raw wikitext for commons images?
                                $this->mText = '';
                        }
@@ -136,7 +144,7 @@ class SearchResult {
                $this->initText();
 
                // TODO: make highliter take a content object. Make ContentHandler a factory for SearchHighliter.
-               list( $contextlines, $contextchars ) = SearchEngine::userHighlightPrefs();
+               list( $contextlines, $contextchars ) = $this->searchEngine->userHighlightPrefs();
 
                $h = new SearchHighlighter();
                if ( count( $terms ) > 0 ) {
index fb153fc..6ca7a13 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * Parent class for all special pages.
  *
@@ -59,6 +61,9 @@ class SpecialPage {
        /**
         * Get a localised Title object for a specified special page name
         *
+        * @since 1.9
+        * @since 1.21 $fragment parameter added
+        *
         * @param string $name
         * @param string|bool $subpage Subpage string, or false to not use a subpage
         * @param string $fragment The link fragment (after the "#")
@@ -342,7 +347,7 @@ class SpecialPage {
                        return [];
                }
 
-               $searchEngine = SearchEngine::create();
+               $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
                $searchEngine->setLimitOffset( $limit, $offset );
                $searchEngine->setNamespaces( [] );
                $result = $searchEngine->defaultPrefixSearch( $search );
index bb82d03..6de127d 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * Implements Special:FileDuplicateSearch
  *
@@ -244,7 +246,7 @@ class FileDuplicateSearchPage extends QueryPage {
                        // No prefix suggestion outside of file namespace
                        return [];
                }
-               $searchEngine = SearchEngine::create();
+               $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
                $searchEngine->setLimitOffset( $limit, $offset );
                // Autocomplete subpage the same as a normal search, but just for files
                $searchEngine->setNamespaces( [ NS_FILE ] );
index 2bf8385..2069638 100644 (file)
@@ -23,6 +23,8 @@
  * @ingroup SpecialPage
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * implements Special:Search - Run text & title search and display the output
  * @ingroup SpecialPage
@@ -79,10 +81,17 @@ class SpecialSearch extends SpecialPage {
         */
        protected $customCaptions;
 
+       /**
+        * Search engine configurations.
+        * @var SearchEngineConfig
+        */
+       protected $searchConfig;
+
        const NAMESPACES_CURRENT = 'sense';
 
        public function __construct() {
                parent::__construct( 'Search' );
+               $this->searchConfig = MediaWikiServices::getInstance()->getSearchEngineConfig();
        }
 
        /**
@@ -150,7 +159,7 @@ class SpecialSearch extends SpecialPage {
                $nslist = $this->powerSearch( $request );
                if ( !count( $nslist ) ) {
                        # Fallback to user preference
-                       $nslist = SearchEngine::userNamespaces( $user );
+                       $nslist = $this->searchConfig->userNamespaces( $user );
                }
 
                $profile = null;
@@ -202,7 +211,8 @@ class SpecialSearch extends SpecialPage {
                        return;
                }
                # If there's an exact or very near match, jump right there.
-               $title = SearchEngine::getNearMatch( $term );
+               $title = $this->newSearchEngine()->
+                       getNearMatcher( $this->getConfig() )->getNearMatch( $term );
 
                if ( !is_null( $title ) &&
                        Hooks::run( 'SpecialSearchGoResult', [ $term, $title, &$url ] )
@@ -620,7 +630,7 @@ class SpecialSearch extends SpecialPage {
         */
        protected function powerSearch( &$request ) {
                $arr = [];
-               foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) {
+               foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
                        if ( $request->getCheck( 'ns' . $ns ) ) {
                                $arr[] = $ns;
                        }
@@ -1021,7 +1031,7 @@ class SpecialSearch extends SpecialPage {
 
                // Groups namespaces into rows according to subject
                $rows = [];
-               foreach ( SearchEngine::searchableNamespaces() as $namespace => $name ) {
+               foreach ( $this->searchConfig->searchableNamespaces() as $namespace => $name ) {
                        $subject = MWNamespace::getSubject( $namespace );
                        if ( !array_key_exists( $subject, $rows ) ) {
                                $rows[$subject] = "";
@@ -1104,15 +1114,15 @@ class SpecialSearch extends SpecialPage {
         */
        protected function getSearchProfiles() {
                // Builds list of Search Types (profiles)
-               $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
-
+               $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
+               $defaultNs = $this->searchConfig->defaultNamespaces();
                $profiles = [
                        'default' => [
                                'message' => 'searchprofile-articles',
                                'tooltip' => 'searchprofile-articles-tooltip',
-                               'namespaces' => SearchEngine::defaultNamespaces(),
-                               'namespace-messages' => SearchEngine::namespacesAsText(
-                                       SearchEngine::defaultNamespaces()
+                               'namespaces' => $defaultNs,
+                               'namespace-messages' => $this->searchConfig->namespacesAsText(
+                                       $defaultNs
                                ),
                        ],
                        'images' => [
@@ -1328,7 +1338,8 @@ class SpecialSearch extends SpecialPage {
        public function getSearchEngine() {
                if ( $this->searchEngine === null ) {
                        $this->searchEngine = $this->searchEngineType ?
-                               SearchEngine::create( $this->searchEngineType ) : SearchEngine::create();
+                               MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $this->searchEngineType ) :
+                               MediaWikiServices::getInstance()->newSearchEngine();
                }
 
                return $this->searchEngine;
index 82e07fd..75308aa 100644 (file)
@@ -180,6 +180,11 @@ class SpecialUpload extends SpecialPage {
                        throw new UserBlockedError( $user->getBlock() );
                }
 
+               // Global blocks
+               if ( $user->isBlockedGlobally() ) {
+                       throw new UserBlockedError( $user->getGlobalBlock() );
+               }
+
                # Check whether we actually want to allow changing stuff
                $this->checkReadOnly();
 
index c1e096b..7c32c3b 100644 (file)
@@ -20,6 +20,7 @@
  * @file
  */
 
+use MediaWiki\MediaWikiServices;
 use MediaWiki\Session\SessionManager;
 use MediaWiki\Session\Token;
 
@@ -275,8 +276,8 @@ class User implements IDBAccessObject {
        protected $mImplicitGroups;
        /** @var array */
        protected $mFormerGroups;
-       /** @var bool */
-       protected $mBlockedGlobally;
+       /** @var Block */
+       protected $mGlobalBlock;
        /** @var bool */
        protected $mLocked;
        /** @var bool */
@@ -1534,7 +1535,8 @@ class User implements IDBAccessObject {
                foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
                        $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode;
                }
-               foreach ( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
+               $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()->searchableNamespaces();
+               foreach ( $namespaces as $nsnum => $nsname ) {
                        $defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
                }
                $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
@@ -1992,8 +1994,22 @@ class User implements IDBAccessObject {
         * @return bool True if blocked, false otherwise
         */
        public function isBlockedGlobally( $ip = '' ) {
-               if ( $this->mBlockedGlobally !== null ) {
-                       return $this->mBlockedGlobally;
+               return $this->getGlobalBlock( $ip ) instanceof Block;
+       }
+
+       /**
+        * Check if user is blocked on all wikis.
+        * Do not use for actual edit permission checks!
+        * This is intended for quick UI checks.
+        *
+        * @param string $ip IP address, uses current client if none given
+        * @return Block|null Block object if blocked, null otherwise
+        * @throws FatalError
+        * @throws MWException
+        */
+       public function getGlobalBlock( $ip = '' ) {
+               if ( $this->mGlobalBlock !== null ) {
+                       return $this->mGlobalBlock ?: null;
                }
                // User is already an IP?
                if ( IP::isIPAddress( $this->getName() ) ) {
@@ -2002,9 +2018,17 @@ class User implements IDBAccessObject {
                        $ip = $this->getRequest()->getIP();
                }
                $blocked = false;
-               Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked ] );
-               $this->mBlockedGlobally = (bool)$blocked;
-               return $this->mBlockedGlobally;
+               $block = null;
+               Hooks::run( 'UserIsBlockedGlobally', [ &$this, $ip, &$blocked, &$block ] );
+
+               if ( $blocked && $block === null ) {
+                       // back-compat: UserIsBlockedGlobally didn't have $block param first
+                       $block = new Block;
+                       $block->setTarget( $ip );
+               }
+
+               $this->mGlobalBlock = $blocked ? $block : false;
+               return $this->mGlobalBlock ?: null;
        }
 
        /**
index 4c92645..d76825c 100644 (file)
        "userpage-userdoesnotexist": "User account \"$1\" is not registered.\nPlease check if you want to create/edit this page.",
        "userpage-userdoesnotexist-view": "User account \"$1\" is not registered.",
        "blocked-notice-logextract": "This user is currently blocked.\nThe latest block log entry is provided below for reference:",
-       "clearyourcache": "<strong>Note:</strong> After saving, you may have to bypass your browser's cache to see the changes.\n* <strong>Firefox / Safari:</strong> Hold <em>Shift</em> while clicking <em>Reload</em>, or press either <em>Ctrl-F5</em> or <em>Ctrl-R</em> (<em>⌘-R</em> on a Mac)\n* <strong>Google Chrome:</strong> Press <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> on a Mac)\n* <strong>Internet Explorer:</strong> Hold <em>Ctrl</em> while clicking <em>Refresh</em>, or press <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Clear the cache in <em>Tools → Preferences</em>",
+       "clearyourcache": "<strong>Note:</strong> After saving, you may have to bypass your browser's cache to see the changes.\n* <strong>Firefox / Safari:</strong> Hold <em>Shift</em> while clicking <em>Reload</em>, or press either <em>Ctrl-F5</em> or <em>Ctrl-R</em> (<em>⌘-R</em> on a Mac)\n* <strong>Google Chrome:</strong> Press <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> on a Mac)\n* <strong>Internet Explorer:</strong> Hold <em>Ctrl</em> while clicking <em>Refresh</em>, or press <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Go to <em>Menu → Settings</em> (<em>Opera → Preferences</em> on a Mac) and then to <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "<strong>Tip:</strong> Use the \"{{int:showpreview}}\" button to test your new CSS before saving.",
        "userjsyoucanpreview": "<strong>Tip:</strong> Use the \"{{int:showpreview}}\" button to test your new JavaScript before saving.",
        "usercsspreview": "<strong>Remember that you are only previewing your user CSS.\nIt has not yet been saved!</strong>",
index 186feb2..6d9a616 100644 (file)
@@ -51,7 +51,7 @@ TEXT
                );
 
                $this->addOption( 'force', 'Run on all rows, even if the collation is ' .
-                       'supposed to be up-to-date.' );
+                       'supposed to be up-to-date.', false, false, 'f' );
                $this->addOption( 'previous-collation', 'Set the previous value of ' .
                        '$wgCategoryCollation here to speed up this script, especially if your ' .
                        'categorylinks table is large. This will only update rows with that ' .
index b8cc059..899daa5 100644 (file)
@@ -94,7 +94,9 @@
                        url = ( ajaxOptions && ajaxOptions.url ) || this.defaults.ajax.url;
                        origin = ( parameters && parameters.origin ) || this.defaults.parameters.origin;
                        url += ( url.indexOf( '?' ) !== -1 ? '&' : '?' ) +
-                               'origin=' + encodeURIComponent( origin );
+                               // Depending on server configuration, MediaWiki may forbid periods in URLs, due to an IE 6
+                               // XSS bug. So let's escape them here. See WebRequest::checkUrlExtension() and T30235.
+                               'origin=' + encodeURIComponent( origin ).replace( /\./g, '%2E' );
                        newAjaxOptions = $.extend( {}, ajaxOptions, { url: url } );
                } else {
                        newAjaxOptions = ajaxOptions;
index b4ff40a..1f21fc6 100644 (file)
                                // Prevent jQuery from overriding the Content-Type header
                                ajaxOptions.contentType = false;
                        } else {
-                               // Some deployed MediaWiki >= 1.17 forbid periods in URLs, due to an IE XSS bug
-                               // So let's escape them here. See bug #28235
                                // This works because jQuery accepts data as a query string or as an Object
-                               ajaxOptions.data = $.param( parameters ).replace( /\./g, '%2E' );
-
+                               ajaxOptions.data = $.param( parameters );
                                // If we extracted a token parameter, add it back in.
                                if ( token ) {
                                        ajaxOptions.data += '&token=' + encodeURIComponent( token );
                                }
 
+                               // Depending on server configuration, MediaWiki may forbid periods in URLs, due to an IE 6
+                               // XSS bug. So let's escape them here. See WebRequest::checkUrlExtension() and T30235.
+                               ajaxOptions.data = ajaxOptions.data.replace( /\./g, '%2E' );
+
                                if ( ajaxOptions.contentType === 'multipart/form-data' ) {
                                        // We were asked to emulate but can't, so drop the Content-Type header, otherwise
                                        // it'll be wrong and the server will fail to decode the POST body
index 1889575..0741445 100644 (file)
@@ -25,6 +25,10 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
                        'SiteStore' => [ 'getSiteStore', SiteStore::class ],
                        'SiteLookup' => [ 'getSiteLookup', SiteLookup::class ],
                        'StatsdDataFactory' => [ 'getStatsdDataFactory', StatsdDataFactory::class ],
+                       'EventRelayerGroup' => [ 'getEventRelayerGroup', EventRelayerGroup::class ],
+                       'SearchEngine' => [ 'newSearchEngine', SearchEngine::class ],
+                       'SearchEngineFactory' => [ 'getSearchEngineFactory', SearchEngineFactory::class ],
+                       'SearchEngineConfig' => [ 'getSearchEngineConfig', SearchEngineConfig::class ],
                ];
        }
 
@@ -49,6 +53,9 @@ class MediaWikiServicesTest extends PHPUnit_Framework_TestCase {
                        'SiteStore' => [ 'SiteStore', SiteStore::class ],
                        'SiteLookup' => [ 'SiteLookup', SiteLookup::class ],
                        'StatsdDataFactory' => [ 'StatsdDataFactory', StatsdDataFactory::class ],
+                       'EventRelayerGroup' => [ 'EventRelayerGroup', EventRelayerGroup::class ],
+                       'SearchEngineFactory' => [ 'SearchEngineFactory', SearchEngineFactory::class ],
+                       'SearchEngineConfig' => [ 'SearchEngineConfig', SearchEngineConfig::class ],
                ];
        }
 
index 90438a0..602a175 100644 (file)
@@ -20,13 +20,19 @@ class MockSearch extends SearchEngine {
  */
 class SearchUpdateTest extends MediaWikiTestCase {
 
+       /**
+        * @var SearchUpdate
+        */
+       private $su;
+
        protected function setUp() {
                parent::setUp();
                $this->setMwGlobals( 'wgSearchType', 'MockSearch' );
+               $this->su = new SearchUpdate( 0, "" );
        }
 
        public function updateText( $text ) {
-               return trim( SearchUpdate::updateText( $text ) );
+               return trim( $this->su->updateText( $text ) );
        }
 
        /**
index 6a3f95b..9ed5244 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * @group Search
  * @group Database
@@ -46,7 +48,7 @@ class SearchEnginePrefixTest extends MediaWikiLangTestCase {
 
                // Avoid special pages from extensions interferring with the tests
                $this->setMwGlobals( 'wgSpecialPages', [] );
-               $this->search = SearchEngine::create();
+               $this->search = MediaWikiServices::getInstance()->newSearchEngine();
                $this->search->setNamespaces( [] );
        }
 
index a4e4df1..4ea9686 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+use MediaWiki\MediaWikiServices;
+
 /**
  * Test class for SpecialSearch class
  * Copyright © 2012, Antoine Musso
@@ -6,7 +8,6 @@
  * @author Antoine Musso
  * @group Database
  */
-
 class SpecialSearchTest extends MediaWikiTestCase {
 
        /**
@@ -57,7 +58,7 @@ class SpecialSearchTest extends MediaWikiTestCase {
        }
 
        public static function provideSearchOptionsTests() {
-               $defaultNS = SearchEngine::defaultNamespaces();
+               $defaultNS = MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces();
                $EMPTY_REQUEST = [];
                $NO_USER_PREF = null;