Seb35 has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/328347 )

Change subject: [WIP] Activate Composer extensions in the same way than other 
extensions are activated
......................................................................

[WIP] Activate Composer extensions in the same way than other extensions are 
activated

This proof-of-concept is not meant to be merged, I prepare a
better-architectured version.

Basically the strategy is to create /vendor/composerKEY with KEY representing 
the
content of the autoloaders (typically a specific hash of its content). All these
directories are created beforehand (2^n directories with n = number of 
Composer-managed
extensions and skins) and the right directory – depending on the wiki – is 
loaded
by the indirection-like /vendor/autoload.php. I didn’t observed side effects 
apart
the minor detail that MediaWiki only display, in Special:Version, Composer 
libraries
it reads in /vendor/composer/installed.json (only a matter of display there).

With this architecture, extensions and skins are simply activated in the 
configuration
by setting wgUseExtensionMyWonderfulExt to true. Obviously there could be higher
level operations to do like update.php, create directories, setting 
extension-specific
parameters, etc. A detail to be improved is to better manage dependencies 
between
extensions: Composer is aware of dependencies, so some composerKEY directories 
are
the same (e.g. SemanticFormsSelect (SFS) also autoloads SemanticMediaWiki (SMW) 
and
PageForms (PF)); here the activation is independant from Composer and it would 
be great
to synchronise the two mechanism in some way (e.g. activating SFS should not 
only select
the right composerKEY but also activate SMW and PF, currently it selects the
right composerKEY but SFS trigger a fatal error since PF 4.0 is not activated).

Architecture:
* the configuration compilation is moved in the bootstraping stage for the most
  part, but given we don’t know at this stage if the MediaWiki version is
  ExtensionRegistry-aware, this small part remains in the LS.php stage.
* the composer key is written in a dedicated cache file: it cannot be in LS.php
  since this file is loaded after the composer key is needed, and it should not
  be in the existence file since it will change as soon as a config file change.

Code:
* added a CLI script mwcomposer.php
* added MediaWikiFarmComposerScript containing the logic of mwcomposer.php
* splitted the configuration part in two parts:
  * major part during the bootstraping stage
  * extension loading mechanism after MediaWiki is loaded

Change-Id: I027251fabb32d6543d4bff7c610935316b639802
---
A bin/mwcomposer.php
M extension.json
M src/MediaWikiFarm.php
A src/MediaWikiFarmComposerScript.php
M src/main.php
5 files changed, 473 insertions(+), 33 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MediaWikiFarm 
refs/changes/47/328347/1

diff --git a/bin/mwcomposer.php b/bin/mwcomposer.php
new file mode 100644
index 0000000..97dbeca
--- /dev/null
+++ b/bin/mwcomposer.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Wrapper around Composer to create as many autoloaders as MediaWiki 
extensions.
+ *
+ * @author Sébastien Beyou ~ Seb35 <se...@seb35.fr>
+ * @license GPL-3.0+ GNU General Public License v3.0 ou version ultérieure
+ * @license AGPL-3.0+ GNU Affero General Public License v3.0 ou version 
ultérieure
+ */
+// @codeCoverageIgnoreStart
+
+# Protect against web entry
+if( PHP_SAPI != 'cli' && PHP_SAPI != 'phpdbg' ) {
+       exit;
+}
+
+# Load classes
+require_once dirname( dirname( __FILE__ ) ) . 
'/src/MediaWikiFarmComposerScript.php';
+
+# Prepare environment
+$wgMediaWikiFarmComposerScript = new MediaWikiFarmComposerScript( $argc, $argv 
);
+
+$wgMediaWikiFarmComposerScript->load();
+
+$wgMediaWikiFarmComposerScript->main();
+
+if( $wgMediaWikiFarmComposerScript->status != 200 ) {
+       exit( $wgMediaWikiFarmComposerScript->status );
+}
+// @codeCoverageIgnoreEnd
diff --git a/extension.json b/extension.json
index 8a2d26f..b49fe04 100644
--- a/extension.json
+++ b/extension.json
@@ -1,6 +1,6 @@
 {
        "name": "MediaWikiFarm",
-       "version": "0.2.0",
+       "version": "0.3.0-dev",
        "author": [
                "Seb35"
        ],
diff --git a/src/MediaWikiFarm.php b/src/MediaWikiFarm.php
index 5f5c309..3da02c4 100644
--- a/src/MediaWikiFarm.php
+++ b/src/MediaWikiFarm.php
@@ -317,6 +317,26 @@
                return true;
        }
 
+       function compileConfig() {
+
+               $this->getMediaWikiConfig();
+
+               $composer = array();
+               foreach( $this->configuration['extensions'] as $name => 
$loading ) {
+                       if( $loading == 'composer' ) {
+                               $composer[] = 'Extension' . $name;
+                       }
+               }
+               foreach( $this->configuration['skins'] as $name => $loading ) {
+                       if( $loading == 'composer' ) {
+                               $composer[] = 'Skin' . $name;
+                       }
+               }
+               $this->variables['$COMPOSER'] = self::composerKey( $composer );
+
+               return true;
+       }
+
        /**
         * This function loads MediaWiki configuration.
         *
@@ -387,7 +407,7 @@
                        $GLOBALS['wgExtensionCredits']['other'][] = array(
                                'path' => $this->farmDir . '/MediaWikiFarm.php',
                                'name' => 'MediaWikiFarm',
-                               'version' => '0.2.0',
+                               'version' => '0.3.0-dev',
                                'author' => 'Seb35',
                                'url' => 
'https://www.mediawiki.org/wiki/Extension:MediaWikiFarm',
                                'descriptionmsg' => 'mediawikifarm-desc',
@@ -434,10 +454,20 @@
        function getConfigFile() {
 
                if( !$this->isLocalSettingsFresh() ) {
+                       $this->compileConfig();
                        return $this->farmDir . '/src/main.php';
                }
 
                return $this->cacheDir . '/LocalSettings/' . 
$this->variables['$SERVER'] . '.php';
+       }
+
+       function finaliseMediaWikiConfig() {
+
+               $this->getMediaWikiConfig();
+
+               $this->finaliseExtractionSkinsAndExtensions();
+
+               $this->writeLocalSettings();
        }
 
 
@@ -532,9 +562,14 @@
                                unset( $result['$CONFIG'] );
                                unset( $result['$CORECONFIG'] );
                                $this->variables = $result;
+                               $composerFile = $this->readFile( $host . 
'.php', $this->cacheDir . '/composer', false );
+                               if( is_array( $composerFile ) ) {
+                                       $this->variables['$COMPOSER'] = 
$composerFile[0];
+                               }
                                return;
                        } elseif( is_file( $this->cacheDir . '/LocalSettings/' 
. $host . '.php' ) ) {
                                unlink( $this->cacheDir . '/LocalSettings/' . 
$host . '.php' );
+                               unlink( $this->cacheDir . '/composer/' . $host 
. '.php' );
                        }
                }
 
@@ -906,6 +941,13 @@
 
                # Extract from the general configuration skin and extension 
configuration
                $this->extractSkinsAndExtensions();
+       }
+
+       function writeLocalSettings( $force = false ) {
+
+               if( !$force && $this->isLocalSettingsFresh() ) {
+                       return;
+               }
 
                # Save this configuration in a PHP file
                if( is_string( $this->cacheDir ) && !count( $this->errors ) ) {
@@ -917,6 +959,12 @@
                                $this->variables['$SERVER'] . '.php',
                                $this->cacheDir . '/LocalSettings'
                        );
+                       if( array_key_exists( '$COMPOSER', $this->variables ) ) 
{
+                               self::cacheFile( array( 
$this->variables['$COMPOSER'] ),
+                                       $this->variables['$SERVER'] . '.php',
+                                       $this->cacheDir . '/composer'
+                               );
+                       }
                }
        }
 
@@ -1141,11 +1189,6 @@
 
                $settings = &$this->configuration['settings'];
 
-               # Autodetect if ExtensionRegistry is here
-               if( is_null( $this->parameters['ExtensionRegistry'] ) ) {
-                       $this->parameters['ExtensionRegistry'] = class_exists( 
'ExtensionRegistry' );
-               }
-
                # Search for skin and extension activation
                $settings2 = $settings; # This line is about avoiding the 
behavious in PHP 5 where newly-added items are evaluated
                foreach( $settings2 as $setting => $value ) {
@@ -1153,39 +1196,32 @@
 
                                $type = strtolower( $matches[1] );
                                $name = $matches[2];
-                               $loadingMechanism = 
$this->detectLoadingMechanism( $type, $name );
-                               if( $value !== true ) {
-                                       $loadingMechanism = $value;
-                               }
 
-                               if( is_null( $loadingMechanism ) ) {
-                                       $settings[$setting] = false;
-                               } else {
-                                       $this->configuration[$type.'s'][$name] 
= $loadingMechanism;
-                                       $settings[preg_replace( 
'/[^a-zA-Z0-9_\x7f\xff]/', '', $setting )] = true;
+                               $loadingMechanism = $value;
+                               if( $value === true ) {
+                                       $loadingMechanism = 
$this->detectLoadingMechanismEarly( $type, $name );
                                }
+                               $this->configuration[$type.'s'][$name] = 
$loadingMechanism;
                        }
                        elseif( preg_match( '/^wgUse(.+)$/', $setting, $matches 
) && ( $value === true || $value == 'require_once' || $value == 'composer' ) ) {
 
                                $name = $matches[1];
 
-                               $loadingMechanism = 
$this->detectLoadingMechanism( 'extension', $name );
-                               if( !is_null( $loadingMechanism ) ) {
+                               $loadingMechanism = 
$this->detectLoadingMechanismEarly( 'extension', $name );
+                               if( $loadingMechanism ) {
                                        if( $value !== true ) {
                                                $loadingMechanism = $value;
                                        }
                                        
$this->configuration['extensions'][$name] = $loadingMechanism;
                                        
$settings['wgUseExtension'.preg_replace( '/[^a-zA-Z0-9_\x7f\xff]/', '', $name 
)] = true;
-                                       unset( $settings[$setting] );
                                } else {
-                                       $loadingMechanism = 
$this->detectLoadingMechanism( 'skin', $name );
-                                       if( !is_null( $loadingMechanism ) ) {
+                                       $loadingMechanism = 
$this->detectLoadingMechanismEarly( 'skin', $name );
+                                       if( $loadingMechanism ) {
                                                if( $value !== true ) {
                                                        $loadingMechanism = 
$value;
                                                }
                                                
$this->configuration['skins'][$name] = $loadingMechanism;
                                                
$settings['wgUseSkin'.preg_replace( '/[^a-zA-Z0-9_\x7f\xff]/', '', $name )] = 
true;
-                                               unset( $settings[$setting] );
                                        }
                                }
                        }
@@ -1202,6 +1238,58 @@
                }
        }
 
+       function finaliseExtractionSkinsAndExtensions() {
+
+               # Autodetect if ExtensionRegistry is here
+               if( is_null( $this->parameters['ExtensionRegistry'] ) ) {
+                       $this->parameters['ExtensionRegistry'] = class_exists( 
'ExtensionRegistry' );
+               }
+
+               foreach( $this->configuration['extensions'] as $name => 
$loading ) {
+
+                       $loadingMechanism = $this->detectLoadingMechanism( 
'extension', $name );
+                       if( $loadingMechanism ) {
+                               $this->configuration['extensions'][$name] = 
$loadingMechanism;
+                               $settings['wgUseExtension'.preg_replace( 
'/[^a-zA-Z0-9_\x7f\xff]/', '', $name )] = true;
+                       } else {
+                               unset( 
$this->configuration['extensions'][$name] );
+                               $settings['wgUseExtension'.preg_replace( 
'/[^a-zA-Z0-9_\x7f\xff]/', '', $name )] = false;
+                       }
+               }
+
+               foreach( $this->configuration['skins'] as $name => $loading ) {
+
+                       $loadingMechanism = $this->detectLoadingMechanism( 
'skin', $name );
+                       if( $loadingMechanism ) {
+                               $this->configuration['skins'][$name] = 
$loadingMechanism;
+                               $settings['wgUseSkin'.preg_replace( 
'/[^a-zA-Z0-9_\x7f\xff]/', '', $name )] = true;
+                       } else {
+                               unset( $this->configuration['skins'][$name] );
+                               $settings['wgUseSkin'.preg_replace( 
'/[^a-zA-Z0-9_\x7f\xff]/', '', $name )] = false;
+                       }
+               }
+       }
+
+       /**
+        * Detect if extensions and skins load with Composer.
+        *
+        * @mediawikifarm-const
+        *
+        * @param string $type Type, in ['extension', 'skin'].
+        * @param string $name Name of the extension/skin.
+        * @return string 'composer' if successfully loaded with Composer or '' 
if it does not load with Composer.
+        */
+       function detectLoadingMechanismEarly( $type, $name ) {
+
+               # The Composer mechanism created a Composer autoloader -> this 
extension/skin is loaded with Composer
+               if( is_dir( $this->variables['$CODE'].'/'.$type.'s/'.$name )
+                   && is_dir( 
$this->variables['$CODE'].'/vendor/composer'.self::composerKey( ucfirst( $type 
).$name ) ) ) {
+                               return 'composer';
+               }
+
+               return '';
+       }
+
        /**
         * Detection of the loading mechanism of extensions and skins.
         *
@@ -1213,23 +1301,19 @@
         */
        function detectLoadingMechanism( $type, $name ) {
 
-               if( !is_dir( $this->variables['$CODE'].'/'.$type.'s/'.$name ) ) 
{
-                       return null;
-               }
-
                # An extension.json/skin.json file is in the directory -> 
assume it is the loading mechanism
                if( $this->parameters['ExtensionRegistry'] && is_file( 
$this->variables['$CODE'].'/'.$type.'s/'.$name.'/'.$type.'.json' ) ) {
                        return 'wfLoad' . ucfirst( $type );
                }
 
+               # The Composer mechanism created a Composer autoloader -> this 
extension/skin is loaded with Composer
+               elseif( $this->detectLoadingMechanismEarly( $type, $name ) ) {
+                       return 'composer';
+               }
+
                # A MyExtension.php file is in the directory -> assume it is 
the loading mechanism
                elseif( is_file( 
$this->variables['$CODE'].'/'.$type.'s/'.$name.'/'.$name.'.php' ) ) {
                        return 'require_once';
-               }
-
-               # A composer.json file is in the directory -> assume it is the 
loading mechanism if previous mechanisms didn’t succeed
-               elseif( is_file( 
$this->variables['$CODE'].'/'.$type.'s/'.$name.'/composer.json' ) ) {
-                       return 'composer';
                }
 
                return null;
@@ -1682,4 +1766,29 @@
 
                return $result;
        }
+
+       /**
+        * Composer key depending on the activated extensions and skins.
+        *
+        * Extension names must follow the form 'ExtensionMyWonderfulExtension';
+        * Skin names must follow the form 'SkinMyWonderfulSkin'.
+        *
+        * @mediawikifarm-const
+        * @mediawikifarm-idempotent
+        *
+        * @param string|string[] $names Names of extensions and skins.
+        * @return string Composer key.
+        */
+       static function composerKey( $names ) {
+
+               if( is_string( $names ) ) {
+                       $names = array( $names );
+               } elseif( !count( $names ) ) {
+                       return '';
+               } else {
+                       sort( $names );
+               }
+
+               return substr( md5( implode( '|', $names ) ), 0, 10 );
+       }
 }
diff --git a/src/MediaWikiFarmComposerScript.php 
b/src/MediaWikiFarmComposerScript.php
new file mode 100644
index 0000000..0280fdf
--- /dev/null
+++ b/src/MediaWikiFarmComposerScript.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * Wrapper around Composer to create as many autoloaders as MediaWiki 
extensions.
+ *
+ * @author Sébastien Beyou ~ Seb35 <se...@seb35.fr>
+ * @license GPL-3.0+ GNU General Public License v3.0 ou version ultérieure
+ * @license AGPL-3.0+ GNU Affero General Public License v3.0 ou version 
ultérieure
+ */
+
+// @codeCoverageIgnoreStart
+require_once dirname( __FILE__ ) . '/AbstractMediaWikiFarmScript.php';
+// @codeCoverageIgnoreEnd
+
+/**
+ * This class contains the major part of the script utility, mainly in the 
main() method.
+ * Using a class instead of a raw script it better for testability purposes 
and to use
+ * less global variables (in fact none; the only global variable written are 
for
+ * compatibility purposes, e.g. PHPUnit expects $_SERVER['argv']).
+ */
+class MediaWikiFarmComposerScript extends AbstractMediaWikiFarmScript {
+
+       /** @var string PHP code to be added in 'vendor/autoload.php'. */
+       public $composerSelectorCode = <<<PHP
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer' . ( array_key_exists( 'wgMediaWikiFarm', 
\$GLOBALS ) ? \$GLOBALS['wgMediaWikiFarm']->getVariable('\$COMPOSER') : '' ) . 
'/autoload_real.php';
+
+
+PHP;
+
+       /**
+        * Create the object with a copy of $argc and $argv.
+        *
+        * @param int $argc Number of input arguments.
+        * @param string[] $argv Input arguments.
+        * @return MediaWikiFarmScript
+        */
+       function __construct( $argc, $argv ) {
+
+               parent::__construct( $argc, $argv );
+
+               $this->shortUsage = <<<HELP
+
+    Usage: php {$this->argv[0]} MediaWikiScript --wiki=hostname …
+
+    Parameters:
+
+      - MediaWikiScript: name of the script, e.g. "maintenance/runJobs.php"
+      - hostname: hostname of the wiki, e.g. "mywiki.example.org"
+
+HELP;
+
+               $fullPath = realpath( $this->argv[0] );
+               $this->longUsage = <<<HELP
+    | Note simple names as "runJobs" will be converted to 
"maintenance/runJobs.php".
+    |
+    | For easier use, you can alias it in your shell:
+    |
+    |     alias mwscript='php $fullPath'
+
+HELP;
+       }
+
+       /**
+        * Main program for the script.
+        *
+        * Although it returns void, the 'status' property says if there was an 
error or not.
+        *
+        * @return void.
+        */
+       function main() {
+
+               # Manage mandatory arguments.
+               $this->premain();
+               if( $this->status ) {
+                       return;
+               }
+
+               # Get wiki
+               $this->host = $this->getParam( 'wiki' );
+               if( is_null( $this->host ) ) {
+                       $this->usage();
+                       $this->status = 400;
+                       return;
+               }
+               $this->getParam( 0 );
+
+               # Initialise the requested version
+               $code = MediaWikiFarm::load( '', $this->host );
+               if( $code != 200 ) {
+                       $this->status = $code;
+                       return;
+               }
+
+               $wgMediaWikiFarm = $GLOBALS['wgMediaWikiFarm'];
+
+               # Backup composer.json and copy MediaWiki directory in 
temporary dir to
+               # change its vendor directory and extensions without breaking 
current
+               # installation
+               $origComposerJson = file_get_contents( 'composer.json' );
+               $tmpDir = $GLOBALS['wgMediaWikiFarmCacheDir'];
+               if( !$tmpDir ) {
+                       $tmpDir = '/tmp';
+               }
+               self::copyr( getcwd(), $tmpDir . '/mediawiki', true, array( 
'/extensions', '/skins', '/vendor', '/composer\.lock' ) );
+
+               $oldCwd = getcwd();
+               chdir( $tmpDir . '/mediawiki' );
+
+               # Update complete dependencies from Composer
+               echo "1. Composer with complete extensions/skins set:\n";
+               system( '/usr/local/bin/composer update ' . implode( ' ', 
$this->argv ) );
+
+               # Copy complete dependencies into 'read-only' directories
+               self::copyr( 'extensions', 'extensions-composer', true );
+               self::copyr( 'skins', 'skins-composer', true );
+               self::copyr( 'vendor', 'vendor-composer', true );
+               if( is_file( 'composer.local.json' ) ) {
+                       unlink( 'composer.local.json' );
+               }
+
+               # Get installed extensions
+               $installedJson = file_get_contents( 
'vendor/composer/installed.json' );
+               if( !$installedJson ) {
+                       $this->status = 500;
+                       return;
+               }
+               $installedJson = json_decode( $installedJson, true );
+               $baseComposerJson = json_decode( $origComposerJson, true );
+
+               $installable = array();
+               $counter = array();
+               $transformation = array();
+               foreach( $installedJson as $package ) {
+                       if( $package['type'] == 'mediawiki-extension' || 
$package['type'] == 'mediawiki-skins' ) {
+                               if( array_key_exists( 'require-dev', 
$baseComposerJson ) && array_key_exists( $package['name'], 
$baseComposerJson['require-dev'] ) ) {
+                                       $counter[$package['name']] = true;
+                                       $installable[$package['name']] = 
$package['version_normalized'];
+                                       $transformation[$package['name']] = 
self::composer2mediawiki( $package['name'], $package['type'] );
+                                       unset( 
$baseComposerJson['require-dev'][$package['name']] );
+                               } else {
+                                       $counter[$package['name']] = true;
+                                       $installable[$package['name']] = 
$package['version_normalized'];
+                                       $transformation[$package['name']] = 
self::composer2mediawiki( $package['name'], $package['type'] );
+                                       if( array_key_exists( 'require', 
$baseComposerJson ) && array_key_exists( $package['name'], 
$baseComposerJson['require'] ) ) {
+                                               unset( 
$baseComposerJson['require'][$package['name']] );
+                                       }
+                               }
+                       }
+               }
+               ksort( $counter );
+               echo "Complete extensions/skins set composed of:\n";
+               foreach( $installable as $name => $version ) {
+                       echo '* ' . preg_replace( '/^(Extension|Skin)/', '$1 ', 
$transformation[$name] ) . " ($version)\n";
+               }
+               echo "\n";
+               self::copyr( 'vendor/composer', 'vendor-composer/composer' . 
self::composerCounterKey( $counter, $transformation ), true, array(), array( 
'/autoload_.*\.php', '/ClassLoader\.php', '/installed\.json' ) );
+               self::copyr( 'vendor/composer', 
'vendor-composer/composer-init', true );
+
+               # Iterate over installable extensions/skins
+               $icounter = 2;
+               while( self::decrementBase2( $counter ) ) {
+
+                       $echo = '';
+                       $thisInstallation = $baseComposerJson;
+                       foreach( $counter as $name => $value ) {
+                               if( $value ) {
+                                       $echo .= '  * ' . preg_replace( 
'/^(Extension|Skin)/', '$1 ', $transformation[$name] ) . "\n";
+                                       $thisInstallation['require'][$name] = 
$installable[$name];
+                               }
+                       }
+                       if( $echo ) {
+                               echo "$icounter. Composer with extensions/skins 
set composed of:\n" . $echo;
+                       } else {
+                               echo "$icounter. Composer with empty 
extensions/skins set:\n";
+                       }
+
+                       file_put_contents( 'composer.json', str_replace( '    
', "\t", json_encode( $thisInstallation, 
JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES ) ) . "\n" );
+                       system( '/usr/local/bin/composer update ' . implode( ' 
', $this->argv ) );
+
+                       self::copyr( 'vendor/composer', 
'vendor-composer/composer' . self::composerCounterKey( $counter, 
$transformation ), true, array(), array( '/autoload_.*\.php', 
'/ClassLoader\.php', '/installed\.json' ) );
+                       $icounter++;
+               }
+
+               # Merge things other than autoloader_*.php, e.g. subdirectory 
'installers'
+               self::copyr( 'vendor/composer', 
'vendor-composer/composer-init', false );
+               self::copyr( 'vendor-composer/composer-init', 
'vendor-composer/composer', true );
+               self::rmdirr( 'vendor-composer/composer-init', true );
+
+               # Put autoloader indirection
+               $newAutoload = explode( "\n", file_get_contents( 
'vendor-composer/autoload.php' ) );
+               file_put_contents( 'vendor-composer/autoload.php', 
$this->composerSelectorCode . $newAutoload[6] . "\n" );
+
+               self::copyr( 'vendor-composer', $wgMediaWikiFarm->getVariable( 
'$CODE' ) . '/vendor', true );
+               if( is_dir( 'extensions-composer' ) ) {
+                       $files = array_diff( scandir( 'extensions-composer' ), 
array( '.', '..' ) );
+                       foreach( $files as $file ) {
+                               self::copyr( 'extensions-composer/' . $file, 
$wgMediaWikiFarm->getVariable( '$CODE' ) . '/extensions/' . $file, true );
+                       }
+               }
+               if( is_dir( 'skins' ) ) {
+                       $files = array_diff( scandir( 'skins-composer' ), 
array( '.', '..' ) );
+                       foreach( $files as $file ) {
+                               self::copyr( 'skins-composer/' . $file, 
$wgMediaWikiFarm->getVariable( '$CODE' ) . '/skins/' . $file, true );
+                       }
+               }
+               if( is_file( $wgMediaWikiFarm->getVariable( '$CODE' ) . 
'/composer.lock' ) ) {
+                       unlink( $wgMediaWikiFarm->getVariable( '$CODE' ) . 
'/composer.lock' );
+               }
+
+               chdir( $oldCwd );
+               self::rmdirr( $tmpDir . '/mediawiki' );
+       }
+
+       /**
+        * Decrement a positive number in base 2.
+        *
+        * The number is an array with boolean values (false=0, true=1) with
+        * first index is the lowest value. This function returns false in
+        * case of newly-negative number.
+        *
+        * @param bool[] $number Number in base 2.
+        * @return bool The decremented number is greater or equal to zero.
+        */
+       static function decrementBase2( &$number ) {
+
+               $lastKey = null;
+               foreach( $number as $key => $value ) {
+                       if( $value ) {
+                               $lastKey = $key;
+                               break;
+                       }
+               }
+               if( is_null( $lastKey ) ) {
+                       return false;
+               }
+               $number[$lastKey] = false;
+               foreach( $number as $key => $value ) {
+                       if( $key == $lastKey ) {
+                               break;
+                       }
+                       $number[$key] = true;
+               }
+
+               return true;
+       }
+
+       static function composerCounterKey( $counter, $transformation = array() 
) {
+
+               $names = array();
+               foreach( $counter as $name => $activation ) {
+                       if( $activation ) {
+                               $names[] = $transformation[$name];
+                       }
+               }
+
+               return self::composerKey( $names );
+       }
+
+       /**
+        * Composer key depending on the activated extensions and skins.
+        *
+        * Extension names must follow the form 'ExtensionMyWonderfulExtension';
+        * Skin names must follow the form 'SkinMyWonderfulSkin'.
+        *
+        * @mediawikifarm-const
+        * @mediawikifarm-idempotent
+        *
+        * @param string|string[] $names Names of extensions and skins.
+        * @return string Composer key.
+        */
+       static function composerKey( $names ) {
+
+               if( is_string( $names ) ) {
+                       $names = array( $names );
+               } elseif( !count( $names ) ) {
+                       return '';
+               } else {
+                       sort( $names );
+               }
+
+               return substr( md5( implode( '|', $names ) ), 0, 10 );
+       }
+
+       static function composer2mediawiki( $name, $type ) {
+
+               $name = preg_replace( '/^mediawiki\//', '', $name );
+               if( $type == 'mediawiki-extension' ) {
+                       $name = preg_replace( '/-extension$/', '', $name );
+                       $name = str_replace( '-', ' ', $name );
+                       $name = str_replace( ' ', '', ucwords( $name ) );
+                       $name = 'Extension' . $name;
+               } elseif( $type == 'mediawiki-skin' )  {
+                       $name = preg_replace( '/-skin$/', '', $name );
+                       $name = 'Skin' . $name;
+               }
+
+               return $name;
+       }
+}
diff --git a/src/main.php b/src/main.php
index e1a0eea..ae146f4 100644
--- a/src/main.php
+++ b/src/main.php
@@ -24,7 +24,7 @@
 }
 
 # Compile MediaWiki configuration
-$wgMediaWikiFarm->getMediaWikiConfig( true );
+$wgMediaWikiFarm->finaliseMediaWikiConfig();
 
 # Load skins with the require_once mechanism
 foreach( $wgMediaWikiFarm->getConfiguration( 'skins' ) as $skin => $value ) {
@@ -37,7 +37,7 @@
 # Load extensions with the require_once mechanism
 foreach( $wgMediaWikiFarm->getConfiguration( 'extensions' ) as $extension => 
$value ) {
 
-       if( $value == 'require_once' ) {
+       if( $value == 'require_once' && $extension != 'MediaWikiFarm' ) {
                require_once "$IP/extensions/$extension/$extension.php";
        }
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/328347
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I027251fabb32d6543d4bff7c610935316b639802
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/MediaWikiFarm
Gerrit-Branch: master
Gerrit-Owner: Seb35 <seb35wikipe...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to