Merge "Permit case independent unit test directories"
[lhc/web/wiklou.git] / includes / cache / localisation / LocalisationCache.php
index fb4675e..2646845 100644 (file)
 
 use CLDRPluralRuleParser\Evaluator;
 use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
-use MediaWiki\Config\ServiceOptions;
-use MediaWiki\Languages\LanguageNameUtils;
-use Psr\Log\LoggerInterface;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class for caching the contents of localisation files, Messages*.php
  * and *.i18n.php.
  *
- * An instance of this class is available using MediaWikiServices.
+ * An instance of this class is available using Language::getLocalisationCache().
  *
  * The values retrieved from here are merged, containing items from extension
  * files, core messages files and the language fallback sequence (e.g. zh-cn ->
@@ -41,8 +40,8 @@ use Psr\Log\LoggerInterface;
 class LocalisationCache {
        const VERSION = 4;
 
-       /** @var ServiceOptions */
-       private $options;
+       /** Configuration associative array */
+       private $conf;
 
        /**
         * True if recaching should only be done on an explicit call to recache().
@@ -51,6 +50,11 @@ class LocalisationCache {
         */
        private $manualRecache = false;
 
+       /**
+        * True to treat all files as expired until they are regenerated by this object.
+        */
+       private $forceRecache = false;
+
        /**
         * The cache data. 3-d array, where the first key is the language code,
         * the second key is the item key e.g. 'messages', and the third key is
@@ -67,16 +71,10 @@ class LocalisationCache {
        private $store;
 
        /**
-        * @var LoggerInterface
+        * @var \Psr\Log\LoggerInterface
         */
        private $logger;
 
-       /** @var callable[] See comment for parameter in constructor */
-       private $clearStoreCallbacks;
-
-       /** @var LanguageNameUtils */
-       private $langNameUtils;
-
        /**
         * A 2-d associative array, code/key, where presence indicates that the item
         * is loaded. Value arbitrary.
@@ -190,52 +188,60 @@ class LocalisationCache {
 
        private $mergeableKeys = null;
 
-       /**
-        * @todo Make this a const when HHVM support is dropped (T192166)
-        *
-        * @var array
-        * @since 1.34
-        */
-       public static $constructorOptions = [
-               // True to treat all files as expired until they are regenerated by this object.
-               'forceRecache',
-               'manualRecache',
-               'ExtensionMessagesFiles',
-               'MessagesDirs',
-       ];
-
        /**
         * For constructor parameters, see the documentation in DefaultSettings.php
         * for $wgLocalisationCacheConf.
         *
-        * Do not construct this directly. Use MediaWikiServices.
-        *
-        * @param ServiceOptions $options
-        * @param LCStore $store What backend to use for storage
-        * @param LoggerInterface $logger
-        * @param callable[] $clearStoreCallbacks To be called whenever the cache is cleared. Can be
-        *   used to clear other caches that depend on this one, such as ResourceLoader's
-        *   MessageBlobStore.
-        * @param LanguageNameUtils $langNameUtils
+        * @param array $conf
         * @throws MWException
         */
-       function __construct(
-               ServiceOptions $options,
-               LCStore $store,
-               LoggerInterface $logger,
-               array $clearStoreCallbacks,
-               LanguageNameUtils $langNameUtils
-       ) {
-               $options->assertRequiredOptions( self::$constructorOptions );
-
-               $this->options = $options;
-               $this->store = $store;
-               $this->logger = $logger;
-               $this->clearStoreCallbacks = $clearStoreCallbacks;
-               $this->langNameUtils = $langNameUtils;
-
-               // Keep this separate from $this->options so it can be mutable
-               $this->manualRecache = $options->get( 'manualRecache' );
+       function __construct( $conf ) {
+               global $wgCacheDirectory;
+
+               $this->conf = $conf;
+               $this->logger = LoggerFactory::getInstance( 'localisation' );
+
+               $directory = !empty( $conf['storeDirectory'] ) ? $conf['storeDirectory'] : $wgCacheDirectory;
+               $storeArg = [];
+               $storeArg['directory'] = $directory;
+
+               if ( !empty( $conf['storeClass'] ) ) {
+                       $storeClass = $conf['storeClass'];
+               } else {
+                       switch ( $conf['store'] ) {
+                               case 'files':
+                               case 'file':
+                                       $storeClass = LCStoreCDB::class;
+                                       break;
+                               case 'db':
+                                       $storeClass = LCStoreDB::class;
+                                       $storeArg['server'] = $conf['storeServer'] ?? [];
+                                       break;
+                               case 'array':
+                                       $storeClass = LCStoreStaticArray::class;
+                                       break;
+                               case 'detect':
+                                       if ( $directory ) {
+                                               $storeClass = LCStoreCDB::class;
+                                       } else {
+                                               $storeClass = LCStoreDB::class;
+                                               $storeArg['server'] = $conf['storeServer'] ?? [];
+                                       }
+                                       break;
+                               default:
+                                       throw new MWException(
+                                               'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
+                                       );
+                       }
+               }
+               $this->logger->debug( static::class . ": using store $storeClass" );
+
+               $this->store = new $storeClass( $storeArg );
+               foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
+                       if ( isset( $conf[$var] ) ) {
+                               $this->$var = $conf[$var];
+                       }
+               }
        }
 
        /**
@@ -400,7 +406,7 @@ class LocalisationCache {
         * @return bool
         */
        public function isExpired( $code ) {
-               if ( $this->options->get( 'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
+               if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
                        $this->logger->debug( __METHOD__ . "($code): forced reload" );
 
                        return true;
@@ -445,7 +451,7 @@ class LocalisationCache {
                $this->initialisedLangs[$code] = true;
 
                # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
-               if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
+               if ( !Language::isValidBuiltInCode( $code ) ) {
                        $this->initShallowFallback( $code, 'en' );
 
                        return;
@@ -453,7 +459,7 @@ class LocalisationCache {
 
                # Recache the data if necessary
                if ( !$this->manualRecache && $this->isExpired( $code ) ) {
-                       if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
+                       if ( Language::isSupportedLanguage( $code ) ) {
                                $this->recache( $code );
                        } elseif ( $code === 'en' ) {
                                throw new MWException( 'MessagesEn.php is missing.' );
@@ -691,7 +697,7 @@ class LocalisationCache {
                global $IP;
 
                // This reads in the PHP i18n file with non-messages l10n data
-               $fileName = $this->langNameUtils->getMessagesFileName( $code );
+               $fileName = Language::getMessagesFileName( $code );
                if ( !file_exists( $fileName ) ) {
                        $data = [];
                } else {
@@ -725,6 +731,7 @@ class LocalisationCache {
                                if ( in_array( $key, self::$mergeableMapKeys ) ) {
                                        $value = $value + $fallbackValue;
                                } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
+                                       // @phan-suppress-next-line PhanTypeMismatchArgumentInternal
                                        $value = array_unique( array_merge( $fallbackValue, $value ) );
                                } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
                                        $value = array_merge_recursive( $value, $fallbackValue );
@@ -798,12 +805,14 @@ class LocalisationCache {
        public function getMessagesDirs() {
                global $IP;
 
+               $config = MediaWikiServices::getInstance()->getMainConfig();
+               $messagesDirs = $config->get( 'MessagesDirs' );
                return [
                        'core' => "$IP/languages/i18n",
                        'exif' => "$IP/languages/i18n/exif",
                        'api' => "$IP/includes/api/i18n",
                        'oojs-ui' => "$IP/resources/lib/ooui/i18n",
-               ] + $this->options->get( 'MessagesDirs' );
+               ] + $messagesDirs;
        }
 
        /**
@@ -813,10 +822,12 @@ class LocalisationCache {
         * @throws MWException
         */
        public function recache( $code ) {
+               global $wgExtensionMessagesFiles;
+
                if ( !$code ) {
                        throw new MWException( "Invalid language code requested" );
                }
-               $this->recachedLangs[$code] = true;
+               $this->recachedLangs[ $code ] = true;
 
                # Initial values
                $initialData = array_fill_keys( self::$allKeys, null );
@@ -825,16 +836,11 @@ class LocalisationCache {
 
                # Load the primary localisation from the source file
                $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
-               if ( $data === false ) {
-                       $this->logger->debug( __METHOD__ . ": no localisation file for $code, using fallback to en" );
-                       $coreData['fallback'] = 'en';
-               } else {
-                       $this->logger->debug( __METHOD__ . ": got localisation for $code from source" );
+               $this->logger->debug( __METHOD__ . ": got localisation for $code from source" );
 
-                       # Merge primary localisation
-                       foreach ( $data as $key => $value ) {
-                               $this->mergeItem( $key, $coreData[$key], $value );
-                       }
+               # Merge primary localisation
+               foreach ( $data as $key => $value ) {
+                       $this->mergeItem( $key, $coreData[ $key ], $value );
                }
 
                # Fill in the fallback if it's not there already
@@ -864,7 +870,7 @@ class LocalisationCache {
 
                # Load non-JSON localisation data for extensions
                $extensionData = array_fill_keys( $codeSequence, $initialData );
-               foreach ( $this->options->get( 'ExtensionMessagesFiles' ) as $extension => $fileName ) {
+               foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
                        if ( isset( $messageDirs[$extension] ) ) {
                                # This extension has JSON message data; skip the PHP shim
                                continue;
@@ -922,16 +928,14 @@ class LocalisationCache {
                                # Load the secondary localisation from the source file to
                                # avoid infinite cycles on cyclic fallbacks
                                $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
-                               if ( $fbData !== false ) {
-                                       # Only merge the keys that make sense to merge
-                                       foreach ( self::$allKeys as $key ) {
-                                               if ( !isset( $fbData[$key] ) ) {
-                                                       continue;
-                                               }
-
-                                               if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
-                                                       $this->mergeItem( $key, $csData[$key], $fbData[$key] );
-                                               }
+                               # Only merge the keys that make sense to merge
+                               foreach ( self::$allKeys as $key ) {
+                                       if ( !isset( $fbData[ $key ] ) ) {
+                                               continue;
+                                       }
+
+                                       if ( is_null( $coreData[ $key ] ) || $this->isMergeableKey( $key ) ) {
+                                               $this->mergeItem( $key, $csData[ $key ], $fbData[ $key ] );
                                        }
                                }
                        }
@@ -1028,9 +1032,8 @@ class LocalisationCache {
                # HACK: If using a null (i.e. disabled) storage backend, we
                # can't write to the MessageBlobStore either
                if ( !$this->store instanceof LCStoreNull ) {
-                       foreach ( $this->clearStoreCallbacks as $callback ) {
-                               $callback();
-                       }
+                       $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
+                       $blobStore->clear();
                }
        }
 
@@ -1091,4 +1094,5 @@ class LocalisationCache {
                $this->store = new LCStoreNull;
                $this->manualRecache = false;
        }
+
 }