Merge "install.php: Allow extensions and skins to be specified"
This commit is contained in:
commit
c69bf3bc87
5 changed files with 149 additions and 56 deletions
|
|
@ -50,30 +50,30 @@ class CliInstaller extends Installer {
|
|||
/**
|
||||
* @param string $siteName
|
||||
* @param string|null $admin
|
||||
* @param array $option
|
||||
* @param array $options
|
||||
*/
|
||||
function __construct( $siteName, $admin = null, array $option = [] ) {
|
||||
function __construct( $siteName, $admin = null, array $options = [] ) {
|
||||
global $wgContLang;
|
||||
|
||||
parent::__construct();
|
||||
|
||||
if ( isset( $option['scriptpath'] ) ) {
|
||||
if ( isset( $options['scriptpath'] ) ) {
|
||||
$this->specifiedScriptPath = true;
|
||||
}
|
||||
|
||||
foreach ( $this->optionMap as $opt => $global ) {
|
||||
if ( isset( $option[$opt] ) ) {
|
||||
$GLOBALS[$global] = $option[$opt];
|
||||
$this->setVar( $global, $option[$opt] );
|
||||
if ( isset( $options[$opt] ) ) {
|
||||
$GLOBALS[$global] = $options[$opt];
|
||||
$this->setVar( $global, $options[$opt] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $option['lang'] ) ) {
|
||||
if ( isset( $options['lang'] ) ) {
|
||||
global $wgLang, $wgLanguageCode;
|
||||
$this->setVar( '_UserLang', $option['lang'] );
|
||||
$wgLanguageCode = $option['lang'];
|
||||
$this->setVar( '_UserLang', $options['lang'] );
|
||||
$wgLanguageCode = $options['lang'];
|
||||
$wgContLang = MediaWikiServices::getInstance()->getContentLanguage();
|
||||
$wgLang = Language::factory( $option['lang'] );
|
||||
$wgLang = Language::factory( $options['lang'] );
|
||||
RequestContext::getMain()->setLanguage( $wgLang );
|
||||
}
|
||||
|
||||
|
|
@ -89,32 +89,47 @@ class CliInstaller extends Installer {
|
|||
$this->setVar( '_AdminName', $admin );
|
||||
}
|
||||
|
||||
if ( !isset( $option['installdbuser'] ) ) {
|
||||
if ( !isset( $options['installdbuser'] ) ) {
|
||||
$this->setVar( '_InstallUser',
|
||||
$this->getVar( 'wgDBuser' ) );
|
||||
$this->setVar( '_InstallPassword',
|
||||
$this->getVar( 'wgDBpassword' ) );
|
||||
} else {
|
||||
$this->setVar( '_InstallUser',
|
||||
$option['installdbuser'] );
|
||||
$options['installdbuser'] );
|
||||
$this->setVar( '_InstallPassword',
|
||||
$option['installdbpass'] ?? "" );
|
||||
$options['installdbpass'] ?? "" );
|
||||
|
||||
// Assume that if we're given the installer user, we'll create the account.
|
||||
$this->setVar( '_CreateDBAccount', true );
|
||||
}
|
||||
|
||||
if ( isset( $option['pass'] ) ) {
|
||||
$this->setVar( '_AdminPassword', $option['pass'] );
|
||||
if ( isset( $options['pass'] ) ) {
|
||||
$this->setVar( '_AdminPassword', $options['pass'] );
|
||||
}
|
||||
|
||||
// Detect and inject any extension found
|
||||
if ( isset( $option['with-extensions'] ) ) {
|
||||
if ( isset( $options['extensions'] ) ) {
|
||||
$status = $this->validateExtensions(
|
||||
'extension', 'extensions', $options['extensions'] );
|
||||
if ( !$status->isOK() ) {
|
||||
$this->showStatusMessage( $status );
|
||||
}
|
||||
$this->setVar( '_Extensions', $status->value );
|
||||
} elseif ( isset( $options['with-extensions'] ) ) {
|
||||
$this->setVar( '_Extensions', array_keys( $this->findExtensions() ) );
|
||||
}
|
||||
|
||||
// Set up the default skins
|
||||
$skins = array_keys( $this->findExtensions( 'skins' ) );
|
||||
if ( isset( $options['skins'] ) ) {
|
||||
$status = $this->validateExtensions( 'skin', 'skins', $options['skins'] );
|
||||
if ( !$status->isOK() ) {
|
||||
$this->showStatusMessage( $status );
|
||||
}
|
||||
$skins = $status->value;
|
||||
} else {
|
||||
$skins = array_keys( $this->findExtensions( 'skins' ) );
|
||||
}
|
||||
$this->setVar( '_Skins', $skins );
|
||||
|
||||
if ( $skins ) {
|
||||
|
|
@ -123,6 +138,28 @@ class CliInstaller extends Installer {
|
|||
}
|
||||
}
|
||||
|
||||
private function validateExtensions( $type, $directory, $nameLists ) {
|
||||
$extensions = [];
|
||||
$status = new Status;
|
||||
foreach ( (array)$nameLists as $nameList ) {
|
||||
foreach ( explode( ',', $nameList ) as $name ) {
|
||||
$name = trim( $name );
|
||||
if ( $name === '' ) {
|
||||
continue;
|
||||
}
|
||||
$extStatus = $this->getExtensionInfo( $type, $directory, $name );
|
||||
if ( $extStatus->isOK() ) {
|
||||
$extensions[] = $name;
|
||||
} else {
|
||||
$status->merge( $extStatus );
|
||||
}
|
||||
}
|
||||
}
|
||||
$extensions = array_unique( $extensions );
|
||||
$status->value = $extensions;
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1264,15 +1264,33 @@ abstract class Installer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Finds extensions that follow the format /$directory/Name/Name.php,
|
||||
* and returns an array containing the value for 'Name' for each found extension.
|
||||
* Find extensions or skins in a subdirectory of $IP.
|
||||
* Returns an array containing the value for 'Name' for each found extension.
|
||||
*
|
||||
* Reasonable values for $directory include 'extensions' (the default) and 'skins'.
|
||||
*
|
||||
* @param string $directory Directory to search in
|
||||
* @param string $directory Directory to search in, relative to $IP, must be either "extensions"
|
||||
* or "skins"
|
||||
* @return array [ $extName => [ 'screenshots' => [ '...' ] ]
|
||||
*/
|
||||
public function findExtensions( $directory = 'extensions' ) {
|
||||
switch ( $directory ) {
|
||||
case 'extensions':
|
||||
return $this->findExtensionsByType( 'extension', 'extensions' );
|
||||
case 'skins':
|
||||
return $this->findExtensionsByType( 'skin', 'skins' );
|
||||
default:
|
||||
throw new InvalidArgumentException( "Invalid extension type" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find extensions or skins, and return an array containing the value for 'Name' for each found
|
||||
* extension.
|
||||
*
|
||||
* @param string $type Either "extension" or "skin"
|
||||
* @param string $directory Directory to search in, relative to $IP
|
||||
* @return array [ $extName => [ 'screenshots' => [ '...' ] ]
|
||||
*/
|
||||
protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
|
||||
if ( $this->getVar( 'IP' ) === null ) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -1282,40 +1300,15 @@ abstract class Installer {
|
|||
return [];
|
||||
}
|
||||
|
||||
// extensions -> extension.json, skins -> skin.json
|
||||
$jsonFile = substr( $directory, 0, strlen( $directory ) - 1 ) . '.json';
|
||||
|
||||
$dh = opendir( $extDir );
|
||||
$exts = [];
|
||||
while ( ( $file = readdir( $dh ) ) !== false ) {
|
||||
if ( !is_dir( "$extDir/$file" ) ) {
|
||||
continue;
|
||||
}
|
||||
$fullJsonFile = "$extDir/$file/$jsonFile";
|
||||
$isJson = file_exists( $fullJsonFile );
|
||||
$isPhp = false;
|
||||
if ( !$isJson ) {
|
||||
// Only fallback to PHP file if JSON doesn't exist
|
||||
$fullPhpFile = "$extDir/$file/$file.php";
|
||||
$isPhp = file_exists( $fullPhpFile );
|
||||
}
|
||||
if ( $isJson || $isPhp ) {
|
||||
// Extension exists. Now see if there are screenshots
|
||||
$exts[$file] = [];
|
||||
if ( is_dir( "$extDir/$file/screenshots" ) ) {
|
||||
$paths = glob( "$extDir/$file/screenshots/*.png" );
|
||||
foreach ( $paths as $path ) {
|
||||
$exts[$file]['screenshots'][] = str_replace( $extDir, "../$directory", $path );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ( $isJson ) {
|
||||
$info = $this->readExtension( $fullJsonFile );
|
||||
if ( $info === false ) {
|
||||
continue;
|
||||
}
|
||||
$exts[$file] += $info;
|
||||
$status = $this->getExtensionInfo( $type, $directory, $file );
|
||||
if ( $status->isOK() ) {
|
||||
$exts[$file] = $status->value;
|
||||
}
|
||||
}
|
||||
closedir( $dh );
|
||||
|
|
@ -1324,12 +1317,65 @@ abstract class Installer {
|
|||
return $exts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type Either "extension" or "skin"
|
||||
* @param string $parentRelPath The parent directory relative to $IP
|
||||
* @param string $name The extension or skin name
|
||||
* @return Status An object containing an error list. If there were no errors, an associative
|
||||
* array of information about the extension can be found in $status->value.
|
||||
*/
|
||||
protected function getExtensionInfo( $type, $parentRelPath, $name ) {
|
||||
if ( $this->getVar( 'IP' ) === null ) {
|
||||
throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
|
||||
}
|
||||
if ( $type !== 'extension' && $type !== 'skin' ) {
|
||||
throw new InvalidArgumentException( "Invalid extension type" );
|
||||
}
|
||||
$absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
|
||||
$relDir = "../$parentRelPath/$name";
|
||||
if ( !is_dir( $absDir ) ) {
|
||||
return Status::newFatal( 'config-extension-not-found', $name );
|
||||
}
|
||||
$jsonFile = $type . '.json';
|
||||
$fullJsonFile = "$absDir/$jsonFile";
|
||||
$isJson = file_exists( $fullJsonFile );
|
||||
$isPhp = false;
|
||||
if ( !$isJson ) {
|
||||
// Only fallback to PHP file if JSON doesn't exist
|
||||
$fullPhpFile = "$absDir/$name.php";
|
||||
$isPhp = file_exists( $fullPhpFile );
|
||||
}
|
||||
if ( !$isJson && !$isPhp ) {
|
||||
return Status::newFatal( 'config-extension-not-found', $name );
|
||||
}
|
||||
|
||||
// Extension exists. Now see if there are screenshots
|
||||
$info = [];
|
||||
if ( is_dir( "$absDir/screenshots" ) ) {
|
||||
$paths = glob( "$absDir/screenshots/*.png" );
|
||||
foreach ( $paths as $path ) {
|
||||
$info['screenshots'][] = str_replace( $absDir, $relDir, $path );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $isJson ) {
|
||||
$jsonStatus = $this->readExtension( $fullJsonFile );
|
||||
if ( !$jsonStatus->isOK() ) {
|
||||
return $jsonStatus;
|
||||
}
|
||||
$info += $jsonStatus->value;
|
||||
}
|
||||
|
||||
return Status::newGood( $info );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fullJsonFile
|
||||
* @param array $extDeps
|
||||
* @param array $skinDeps
|
||||
*
|
||||
* @return array|bool False if this extension can't be loaded
|
||||
* @return Status On success, an array of extension information is in $status->value. On
|
||||
* failure, the Status object will have an error list.
|
||||
*/
|
||||
private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
|
||||
$load = [
|
||||
|
|
@ -1340,7 +1386,7 @@ abstract class Installer {
|
|||
foreach ( $extDeps as $dep ) {
|
||||
$fname = "$extDir/$dep/extension.json";
|
||||
if ( !file_exists( $fname ) ) {
|
||||
return false;
|
||||
return Status::newFatal( 'config-extension-not-found', $dep );
|
||||
}
|
||||
$load[$fname] = 1;
|
||||
}
|
||||
|
|
@ -1350,7 +1396,7 @@ abstract class Installer {
|
|||
foreach ( $skinDeps as $dep ) {
|
||||
$fname = "$skinDir/$dep/skin.json";
|
||||
if ( !file_exists( $fname ) ) {
|
||||
return false;
|
||||
return Status::newFatal( 'config-extension-not-found', $dep );
|
||||
}
|
||||
$load[$fname] = 1;
|
||||
}
|
||||
|
|
@ -1364,7 +1410,8 @@ abstract class Installer {
|
|||
) {
|
||||
// If something is incompatible with a dependency, we have no real
|
||||
// option besides skipping it
|
||||
return false;
|
||||
return Status::newFatal( 'config-extension-dependency',
|
||||
basename( dirname( $fullJsonFile ) ), $e->getMessage() );
|
||||
} elseif ( $e->missingExtensions || $e->missingSkins ) {
|
||||
// There's an extension missing in the dependency tree,
|
||||
// so add those to the dependency list and try again
|
||||
|
|
@ -1375,7 +1422,8 @@ abstract class Installer {
|
|||
);
|
||||
}
|
||||
// Some other kind of dependency error?
|
||||
return false;
|
||||
return Status::newFatal( 'config-extension-dependency',
|
||||
basename( dirname( $fullJsonFile ) ), $e->getMessage() );
|
||||
}
|
||||
$ret = [];
|
||||
// The order of credits will be the order of $load,
|
||||
|
|
@ -1397,7 +1445,7 @@ abstract class Installer {
|
|||
}
|
||||
$ret['type'] = $credits['type'];
|
||||
|
||||
return $ret;
|
||||
return Status::newGood( $ret );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -309,6 +309,8 @@
|
|||
"config-skins-screenshot": "$1 ($2)",
|
||||
"config-extensions-requires": "$1 (requires $2)",
|
||||
"config-screenshot": "screenshot",
|
||||
"config-extension-not-found": "Could not find the registration file for the extension \"$1\"",
|
||||
"config-extension-dependency": "A dependency error was encountered while installing the extension \"$1\": $2",
|
||||
"mainpagetext": "<strong>MediaWiki has been installed.</strong>",
|
||||
"mainpagedocfooter": "Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -330,6 +330,8 @@
|
|||
"config-skins-screenshot": "Radio button text, $1 is the skin name, and $2 is a link to a screenshot of that skin, where the link text is {{msg-mw|config-screenshot}}.",
|
||||
"config-extensions-requires": "Radio button text, $1 is the extension name, and $2 are links to other extensions that this one requires.\n{{Identical|Require}}",
|
||||
"config-screenshot": "Link text for the link in {{msg-mw|config-skins-screenshot}}\n{{Identical|Screenshot}}",
|
||||
"config-extension-not-found": "An error shown when an extension or skin named by the user could not be found.\n* $1 is the extension name",
|
||||
"config-extension-dependency": "An error shown if an extension could not be loaded due to it depending on the wrong version of MediaWiki or an uninstallable extension.\n* $1 is the extension name\n* $2 is a more detailed explanation, in English",
|
||||
"mainpagetext": "Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.",
|
||||
"mainpagedocfooter": "Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.\nThis might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@ class CommandLineInstaller extends Maintenance {
|
|||
$this->addOption( 'env-checks', "Run environment checks only, don't change anything" );
|
||||
|
||||
$this->addOption( 'with-extensions', "Detect and include extensions" );
|
||||
$this->addOption( 'extensions', 'Comma-separated list of extensions to install',
|
||||
false, true, false, true );
|
||||
$this->addOption( 'skins', 'Comma-separated list of skins to install (default: all)',
|
||||
false, true, false, true );
|
||||
}
|
||||
|
||||
public function getDbType() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue