Merge "Add support for PHP7 random_bytes in favor of mcrypt_create_iv"
[lhc/web/wiklou.git] / includes / registration / ExtensionRegistry.php
index b5c70e9..344dd8f 100644 (file)
@@ -31,7 +31,7 @@ class ExtensionRegistry {
        /**
         * Bump whenever the registration cache needs resetting
         */
-       const CACHE_VERSION = 3;
+       const CACHE_VERSION = 5;
 
        /**
         * Special key that defines the merge strategy
@@ -40,11 +40,6 @@ class ExtensionRegistry {
         */
        const MERGE_STRATEGY = '_merge_strategy';
 
-       /**
-        * @var BagOStuff
-        */
-       protected $cache;
-
        /**
         * Array of loaded things, keyed by name, values are credits information
         *
@@ -59,6 +54,13 @@ class ExtensionRegistry {
         */
        protected $queued = [];
 
+       /**
+        * Whether we are done loading things
+        *
+        * @var bool
+        */
+       private $finished = false;
+
        /**
         * Items in the JSON file that aren't being
         * set as globals
@@ -83,16 +85,6 @@ class ExtensionRegistry {
                return self::$instance;
        }
 
-       public function __construct() {
-               // We use a try/catch because we don't want to fail here
-               // if $wgObjectCaches is not configured properly for APC setup
-               try {
-                       $this->cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
-               } catch ( MWException $e ) {
-                       $this->cache = new EmptyBagOStuff();
-               }
-       }
-
        /**
         * @param string $path Absolute path to the JSON file
         */
@@ -114,24 +106,42 @@ class ExtensionRegistry {
                $this->queued[$path] = $mtime;
        }
 
+       /**
+        * @throws MWException If the queue is already marked as finished (no further things should
+        *  be loaded then).
+        */
        public function loadFromQueue() {
                global $wgVersion;
                if ( !$this->queued ) {
                        return;
                }
 
+               if ( $this->finished ) {
+                       throw new MWException(
+                               "The following paths tried to load late: "
+                               . implode( ', ', array_keys( $this->queued ) )
+                       );
+               }
+
                // A few more things to vary the cache on
                $versions = [
                        'registration' => self::CACHE_VERSION,
                        'mediawiki' => $wgVersion
                ];
 
+               // We use a try/catch because we don't want to fail here
+               // if $wgObjectCaches is not configured properly for APC setup
+               try {
+                       $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
+               } catch ( MWException $e ) {
+                       $cache = new EmptyBagOStuff();
+               }
                // See if this queue is in APC
                $key = wfMemcKey(
                        'registration',
                        md5( json_encode( $this->queued + $versions ) )
                );
-               $data = $this->cache->get( $key );
+               $data = $cache->get( $key );
                if ( $data ) {
                        $this->exportExtractedData( $data );
                } else {
@@ -141,7 +151,7 @@ class ExtensionRegistry {
                        // did that, but it should be cached
                        $data['globals']['wgAutoloadClasses'] += $data['autoload'];
                        unset( $data['autoload'] );
-                       $this->cache->set( $key, $data, 60 * 60 * 24 );
+                       $cache->set( $key, $data, 60 * 60 * 24 );
                }
                $this->queued = [];
        }
@@ -164,6 +174,15 @@ class ExtensionRegistry {
                $this->queued = [];
        }
 
+       /**
+        * After this is called, no more extensions can be loaded
+        *
+        * @since 1.29
+        */
+       public function finish() {
+               $this->finished = true;
+       }
+
        /**
         * Process a queue of extensions and return their extracted data
         *
@@ -176,8 +195,9 @@ class ExtensionRegistry {
                $autoloadClasses = [];
                $autoloaderPaths = [];
                $processor = new ExtensionProcessor();
+               $versionChecker = new VersionChecker( $wgVersion );
+               $extDependencies = [];
                $incompatible = [];
-               $coreVersionParser = new CoreVersionChecker( $wgVersion );
                foreach ( $queue as $path => $mtime ) {
                        $json = file_get_contents( $path );
                        if ( $json === false ) {
@@ -188,25 +208,13 @@ class ExtensionRegistry {
                                throw new Exception( "$path is not a valid JSON file." );
                        }
 
-                       // Check any constraints against MediaWiki core
-                       $requires = $processor->getRequirements( $info );
-                       if ( isset( $requires[self::MEDIAWIKI_CORE] )
-                               && !$coreVersionParser->check( $requires[self::MEDIAWIKI_CORE] )
-                       ) {
-                               // Doesn't match, mark it as incompatible.
-                               $incompatible[] = "{$info['name']} is not compatible with the current "
-                                       . "MediaWiki core (version {$wgVersion}), it requires: " . $requires[self::MEDIAWIKI_CORE]
-                                       . '.';
-                               continue;
-                       }
-
                        if ( !isset( $info['manifest_version'] ) ) {
                                // For backwards-compatability, assume a version of 1
                                $info['manifest_version'] = 1;
                        }
                        $version = $info['manifest_version'];
                        if ( $version < self::OLDEST_MANIFEST_VERSION || $version > self::MANIFEST_VERSION ) {
-                               throw new Exception( "$path: unsupported manifest_version: {$version}" );
+                               $incompatible[] = "$path: unsupported manifest_version: {$version}";
                        }
 
                        $autoload = $this->processAutoLoader( dirname( $path ), $info );
@@ -214,12 +222,30 @@ class ExtensionRegistry {
                        $GLOBALS['wgAutoloadClasses'] += $autoload;
                        $autoloadClasses += $autoload;
 
+                       // get all requirements/dependencies for this extension
+                       $requires = $processor->getRequirements( $info );
+
+                       // validate the information needed and add the requirements
+                       if ( is_array( $requires ) && $requires && isset( $info['name'] ) ) {
+                               $extDependencies[$info['name']] = $requires;
+                       }
+
                        // Get extra paths for later inclusion
                        $autoloaderPaths = array_merge( $autoloaderPaths,
                                $processor->getExtraAutoloaderPaths( dirname( $path ), $info ) );
                        // Compatible, read and extract info
                        $processor->extractInfo( $path, $info, $version );
                }
+               $data = $processor->getExtractedInfo();
+
+               // check for incompatible extensions
+               $incompatible = array_merge(
+                       $incompatible,
+                       $versionChecker
+                               ->setLoadedExtensionsAndSkins( $data['credits'] )
+                               ->checkArray( $extDependencies )
+               );
+
                if ( $incompatible ) {
                        if ( count( $incompatible ) === 1 ) {
                                throw new Exception( $incompatible[0] );
@@ -227,7 +253,7 @@ class ExtensionRegistry {
                                throw new Exception( implode( "\n", $incompatible ) );
                        }
                }
-               $data = $processor->getExtractedInfo();
+
                // Need to set this so we can += to it later
                $data['globals']['wgAutoloadClasses'] = [];
                $data['autoload'] = $autoloadClasses;
@@ -285,9 +311,6 @@ class ExtensionRegistry {
                foreach ( $info['autoloaderPaths'] as $path ) {
                        require_once $path;
                }
-               foreach ( $info['callbacks'] as $cb ) {
-                       call_user_func( $cb );
-               }
 
                $this->loaded += $info['credits'];
                if ( $info['attributes'] ) {
@@ -297,6 +320,10 @@ class ExtensionRegistry {
                                $this->attributes = array_merge_recursive( $this->attributes, $info['attributes'] );
                        }
                }
+
+               foreach ( $info['callbacks'] as $name => $cb ) {
+                       call_user_func( $cb, $info['credits'][$name] );
+               }
        }
 
        /**