Seb35 has uploaded a new change for review. https://gerrit.wikimedia.org/r/299356
Change subject: Initial commit with working code to manage a MediaWiki farm with some config files ...................................................................... Initial commit with working code to manage a MediaWiki farm with some config files This code is working, although it should be better organised, essentially within a class, with an operational config file separated from the code, as a true MediaWiki extension, etc. --- A LocalSettings.php 1 file changed, 412 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MediaWikiFarm refs/changes/56/299356/1 diff --git a/LocalSettings.php b/LocalSettings.php new file mode 100644 index 0000000..64c57d2 --- /dev/null +++ b/LocalSettings.php @@ -0,0 +1,412 @@ +<?php + +use Symfony\Component\Yaml\Yaml; + +# Protect against web entry +if ( !defined( 'MEDIAWIKI' ) ) { + exit; +} + +/** + * Get or compute the configuration (MediaWiki, skins, extensions) for a wiki + * + * You have to specify the wiki, the suffix, and the version and, as parameters, the configuration + * and code directories and the caching file. This function uses a caching mechanism to avoid + * recompute each time the configuration; it is rebuilt when origin configuration files are changed. + * + * The params argument should have the following keys: + * - 'configDir' (string) Configuration directory where are the various configuration files + * - 'codeDir' (string) Code directory where MediaWiki code is + * - 'cacheFile' (string) Template filename of the caching file + * - 'generalYamlFilename' (string) Path for the general YAML file, relative to configDir + * - 'suffixedYamlFilename' (string) Path for the suffixed YAML file, relative to configDir + * - 'privateYamlFilename' (string) Path for the privale YAML file, relative to configDir + * In cacheFile and suffixedYamlFilename, the string '$suffix' will be replaced by the actual + * suffix, and in cacheFile, the strings '$wiki' and '$version' will be replaced by the actual + * wiki identifier and the version. + * + * The returned array has the following format: + * array( 'general' => array( 'wgSitename' => 'Foo', ... ), + * 'skins' => array( '_loading' => 'wfLoadSkin'|'require_once', + * 'wgFlowParsoidTimeout' => 100, ... + * ), + * 'extensions' => array( '_loading' => 'wfLoadExtension'|'require_once', + * 'wgFlowParsoidTimeout' => 100, ... + * ) + * ) + * + * @param $wiki string Name of the wiki + * @param $suffix string Suffix of the wiki (main family type) + * @param $version string Version of the wiki + * @param $wgConf SiteConfiguration object from MediaWiki + * @param $params array Parameters for this configuration management + * @return array Global parameter variables and loading mechanisms for skins and extensions + */ +function wvgGetMediaWikiConfig( $myWiki, $mySuffix, $myVersion, &$wgConf, $params ) { + + $configDir = $params['configDir']; + $codeDir = $params['codeDir']; + $cacheFile = $params['cacheFile']; + $generalYamlFilename = $params['generalYamlFilename']; + $suffixedYamlFilename = $params['suffixedYamlFilename']; + $privateYamlFilename = $params['privateYamlFilename']; + + $cacheFile = preg_replace( array( '/\$wiki/', '/\$suffix/', '/\$version/' ), + array( $myWiki, $mySuffix, $myVersion ), + $cacheFile ); + + $suffixedYamlFilename = preg_replace( '/\$suffix/', $mySuffix, $suffixedYamlFilename ); + + $globals = false; + + if( @filemtime( $cacheFile ) >= max( filemtime( $configDir.$generalYamlFilename ), + filemtime( $configDir.$suffixedYamlFilename ), + filemtime( $configDir.$privateYamlFilename ) ) ) + { + $cache = @file_get_contents( $cacheFile ); + if ( $cache !== false ) { + $globals = unserialize( $cache ); + } + } + else { + + $globals = array(); + $globals['general'] = array(); + $globals['skins'] = array(); + $globals['extensions'] = array(); + + // Load InitialiseSettings.yml (general) + $generalSettings = Yaml::parse( file_get_contents( $configDir.$generalYamlFilename ) ); + foreach( $generalSettings as $setting => $value ) { + + $wgConf->settings[$setting]['default'] = $value; + } + + // Load InitialiseSettings.yml (client) + $suffixedSettings = Yaml::parse( file_get_contents( $configDir.$suffixedYamlFilename ) ); + foreach( $suffixedSettings as $setting => $values ) { + + foreach( $values as $wiki => $val ) { + + if( $wiki == 'default' ) $wgConf->settings[$setting][$mySuffix] = $val; + else $wgConf->settings[$setting][$wiki.'-'.$mySuffix] = $val; + } + } + + // Load PrivateSettings.yml (general) + $privateSettings = Yaml::parse( file_get_contents( $configDir.$privateYamlFilename ) ); + foreach( $privateSettings as $setting => $value ) { + + foreach( $value as $suffix => $val ) { + + $wgConf->settings[$setting][$suffix] = $val; + } + } + + // Get specific configuration for this wiki + // Do not use SiteConfiguration::extractAllGlobals or the configuration caching would become + // ineffective and there would be inconsistencies in this process + $globals['general'] = $wgConf->getAll( $myWiki.'-'.$mySuffix, $mySuffix ); + + // For the permissions array, fix a small strangeness: when an existing default permission + // is true, it is not possible to make it false in the specific configuration + if( array_key_exists( '+wgGroupPermissions', $wgConf->settings ) ) + + $globals['general']['wgGroupPermissions'] = wvgArrayMerge( $wgConf->get( '+wgGroupPermissions', $myWiki.'-'.$mySuffix, $mySuffix ), $globals['general']['wgGroupPermissions'] ); + + //if( array_key_exists( '+wgDefaultUserOptions', $wgConf->settings ) ) + //$globals['general']['wgDefaultUserOptions'] = wvgArrayMerge( $wgConf->get( '+wgDefaultUserOptions', $myWiki.'-'.$mySuffix, $mySuffix ), $globals['general']['wgDefaultUserOptions'] ); + + // Extract from the general configuration skin and extension configuration + // Search for skin and extension activation + $unsetPrefixes = array(); + foreach( $globals['general'] as $setting => $value ) { + if( preg_match( '/^wgUseSkin(.+)$/', $setting, $matches ) && $value === true ) { + + $skin = $matches[1]; + if( is_dir( $codeDir.'/'.$myVersion.'/skins/'.$skin ) ) { + + $globals['skins'][$skin] = array(); + if( is_file( $codeDir.'/'.$myVersion.'/skins/'.$skin.'/skin.json' ) ) { + $globals['skins'][$skin]['_loading'] = 'wfLoadSkin'; + } + elseif( is_file( $codeDir.'/'.$myVersion.'/skins/'.$skin.'/'.$skin.'.php' ) ) { + $globals['skins'][$skin]['_loading'] = 'require_once'; + } + elseif( is_file( $codeDir.'/'.$myVersion.'/skins/'.$skin.'/composer.json' ) ) { + $globals['skins'][$skin]['_loading'] = 'composer'; + } + else {echo ' (unknown)';$unsetPrefixes[] = $skin;} + } + else $unsetPrefixes[] = $skin; + + unset( $globals['general'][$setting] ); + } + elseif( preg_match( '/^wgUseExtension(.+)$/', $setting, $matches ) && $value === true ) { + + $extension = $matches[1]; + if( is_dir( $codeDir.'/'.$myVersion.'/extensions/'.$extension ) ) { + + $globals['extensions'][$extension] = array(); + if( is_file( $codeDir.'/'.$myVersion.'/extensions/'.$extension.'/extension.json' ) && $extension !== 'VisualEditor' ) { + $globals['extensions'][$extension]['_loading'] = 'wfLoadExtension'; + } + elseif( is_file( $codeDir.'/'.$myVersion.'/extensions/'.$extension.'/'.$extension.'.php' ) ) { + $globals['extensions'][$extension]['_loading'] = 'require_once'; + } + elseif( is_file( $codeDir.'/'.$myVersion.'/extensions/'.$extension.'/composer.json' ) ) { + $globals['extensions'][$extension]['_loading'] = 'composer'; + } + else $unsetPrefixes[] = $extension; + } + else $unsetPrefixes[] = $extension; + + unset( $globals['general'][$setting] ); + } + elseif( preg_match( '/^wgUse(?:Skin|Extension|LocalExtension)(.+)$/', $setting, $matches ) && $value !== true ) { + + $unsetPrefixes[] = $matches[1]; + unset( $globals['general'][$setting] ); + } + } + + // Extract from the general configuration skin and extension configuration + $skins = array_keys( $globals['skins'] ); + $extensions = array_keys( $globals['extensions'] ); + foreach( $globals['general'] as $setting => $value ) { + + $found = false; + foreach( $extensions as $extension ) { + if( preg_match( '/^wg'.preg_quote($extension,'/').'/', $setting ) ) { + $globals['extensions'][$extension][$setting] = $value; + unset( $setting ); + $found = true; + break; + } + } + if( !$found ) { + foreach( $skins as $skin ) { + if( preg_match( '/^wg'.preg_quote($skin,'/').'/', $setting ) ) { + $globals['skins'][$skin][$setting] = $value; + unset( $setting ); + $found = true; + break; + } + } + } + if( !$found ) { + foreach( $unsetPrefixes as $prefix ) { + if( preg_match( '/^wg'.preg_quote($prefix,'/').'/', $setting ) ) { + unset( $setting ); + break; + } + } + } + } + + // Save this configuration in a serialised file + @mkdir( dirname( $cacheFile ) ); + $tmpFile = tempnam( dirname( $cacheFile ), basename( $cacheFile ).'.tmp' ); + chmod( $tmpFile, 0640 ); + if( $tmpFile && file_put_contents( $tmpFile, serialize( $globals ) ) ) { + rename( $tmpFile, $cacheFile ); + } + } + + return $globals; +} + +/** + * This function loads MediaWiki configuration (parameters) + * + * @param $extensions array Subarray of general parameters (c.f. wvgGetMediaWikiConfig) + */ +function wvgLoadMediaWikiConfig( $settings ) { + + // Set general parameters as global variables + foreach( $settings as $setting => $value ) { + + $GLOBALS[$setting] = $value; + } +} + +/** + * This function load the skins configuration (wfLoadSkin loading mechanism and parameters) + * + * WARNING: it doesn’t load the skins with the require_once mechanism (it is not possible in a + * function because variables would inherit the non-global scope); + * such skins must be loaded before calling this function. + * + * @param $extensions array Subarray of skins parameters (c.f. wvgGetMediaWikiConfig) + */ +function wvgLoadSkinsConfig( $skins ) { + + // Load skins with the wfLoadSkin mechanism + foreach( $skins as $skin => $value ) { + + if( $value['_loading'] == 'wfLoadSkin' ) + + wfLoadSkin( $skin ); + + unset( $skins[$skin]['_loading'] ); + } + + // Set skin parameters as global variables + foreach( $skins as $skin => $settings ) { + + foreach( $settings as $setting => $value ) + + $GLOBALS[$setting] = $value; + } +} + +/** + * This function load the skins configuration (wfLoadSkin loading mechanism and parameters) + * + * WARNING: it doesn’t load the skins with the require_once mechanism (it is not possible in a + * function because variables would inherit the non-global scope); + * such skins must be loaded before calling this function. + * + * @param $extensions array Subarray of extensions parameters (c.f. wvgGetMediaWikiConfig) + */ +function wvgLoadExtensionsConfig( $extensions ) { + + // Load extensions with the wfLoadExtension mechanism + foreach( $extensions as $extension => $value ) { + + if( $value['_loading'] == 'wfLoadExtension' ) + + wfLoadExtension( $extension ); + + unset( $extensions[$extension]['_loading'] ); + } + + // Set extension parameters as global variables + foreach( $extensions as $extension => $settings ) { + + foreach( $settings as $setting => $value ) + + $GLOBALS[$setting] = $value; + } +} + +/** + * Merge multiple arrays together. + * On encountering duplicate keys, merge the two, but ONLY if they're arrays. + * PHP's array_merge_recursive() merges ANY duplicate values into arrays, + * which is not fun + * This function is almost the same as SiteConfiguration::arrayMerge, with the + * difference an existing scalar value has precedence EVEN if evaluated to false, + * in order to override permissions array with removed rights. + * + * @param array $array1 + * + * @return array + */ +function wvgArrayMerge( $array1/* ... */ ) { + $out = $array1; + $argsCount = func_num_args(); + for ( $i = 1; $i < $argsCount; $i++ ) { + foreach ( func_get_arg( $i ) as $key => $value ) { + if ( isset( $out[$key] ) && is_array( $out[$key] ) && is_array( $value ) ) { + $out[$key] = wvgArrayMerge( $out[$key], $value ); + } elseif ( !isset( $out[$key] ) && !is_numeric( $key ) ) { + // Values that evaluate to true given precedence, for the + // primary purpose of merging permissions arrays. + $out[$key] = $value; + } elseif ( is_numeric( $key ) ) { + $out[] = $value; + } + } + } + + return $out; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +$wvgConfigDir = '/etc/mediawiki'; +$wvgCodeDir = '/srv/www/mediawiki'; + +function wvgGetWikiFromURL() { + + if( !preg_match( '/^([a-zA-Z0-9]+)-([a-zA-Z0-9]+)\.example\.com$/', $GLOBALS['_SERVER']['SERVER_NAME'], $matches ) ) { + echo 'Error: unknown wiki.'; + exit; + } + + return array( $matches[2], $matches[1] ); +} + +list( $wvgWiki, $wvgClient ) = wvgGetWikiFromURL(); + +// Suffixes (clients): only the current client is saved to avoid any information leak to other clients, e.g. array( 'wikipedia' ) +$wgConf->suffixes = array( $wvgClient ); +if( !in_array( $wvgClient, Yaml::parse( file_get_contents( $wvgConfigDir . '/clients.yml' ) ) ) ) { + echo 'Error: unknown wiki.'; + exit; +} + +// Wikis: a simple list of the wikis for the requested client, e.g. array( 'da', 'cv' ) +$wvgClientWikis = Yaml::parse( file_get_contents( $wvgConfigDir.'/'.$wvgClient.'/wikis.yml' ) ); +$wvgVersion = false; +foreach( $wvgClientWikis as $wiki => $value ) { + $wgConf->wikis[] = $wiki.'-'.$wvgClient; +} + +if( !in_array( $wvgWiki.'-'.$wvgClient, $wgConf->wikis ) ) { + echo 'Error: unknown wiki.'; + exit; +} + +// Get version +$wvgVersion = $wvgClientWikis[$wvgWiki]; + +if( !preg_match( '/^1\.\d{1,2}/', $wvgVersion ) ) { + echo 'Error: unknown wiki.'; + exit; +} + +// Obtain the global configuration +$wvgGlobals = wvgGetMediaWikiConfig( $wvgWiki, $wvgClient, $wvgVersion, $wgConf, + array( 'configDir' => $wvgConfigDir, + 'codeDir' => $wvgCodeDir, + 'cacheFile' => '/tmp/mw-cache/conf-$version-$wiki-$suffix', + 'generalYamlFilename' => '/InitialiseSettings.yml', + 'suffixedYamlFilename' => '/$suffix/InitialiseSettings.yml', + 'privateYamlFilename' => '/PrivateSettings.yml', + ) +); + +// Load general MediaWiki configuration +wvgLoadMediaWikiConfig( $wvgGlobals['general'] ); + +// Set system parameters +$wgUploadDirectory = $wvgConfigDir.'/'.$wvgClient.'/'.$wvgWiki.'/images'; +$wgCacheDirectory = $wvgConfigDir.'/'.$wvgClient.'/'.$wvgWiki.'/cache'; + +// Load skins with the require_once mechanism +foreach( $wvgGlobals['skins'] as $skin => $value ) { + + if( $value['_loading'] == 'require_once' ) + require_once "$IP/skins/$skin/$skin.php"; +} + +// Load skin configuration +wvgLoadSkinsConfig( $wvgGlobals['skins'] ); + +// Load extensions with the require_once mechanism +foreach( $wvgGlobals['extensions'] as $extension => $value ) { + + if( $value['_loading'] == 'require_once' ) + require_once "$IP/extensions/$extension/$extension.php"; +} + +// Load extension configuration +wvgLoadExtensionsConfig( $wvgGlobals['extensions'] ); + +// L’éditeur visuel cherchant toujours à se faire remarquer par les sysadmins, la +// ligne suivante est nécessaire tant qu’il est chargé avec require_once, car +// l’inclusion écrase cette valeur (même si spécifiée dans les fichiers YAML) +$wgDefaultUserOptions['visualeditor-enable'] = 1; + -- To view, visit https://gerrit.wikimedia.org/r/299356 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I11dc434721d049a2362c16b794f8916fb2275268 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