| <?php | |
| /** | |
| * @defgroup Wikimedia Wikimedia | |
| */ | |
| /** | |
| * Add a new wiki | |
| * Wikimedia specific! | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License along | |
| * with this program; if not, write to the Free Software Foundation, Inc., | |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
| * http://www.gnu.org/copyleft/gpl.html | |
| * | |
| * @file | |
| * @ingroup Maintenance | |
| * @ingroup Wikimedia | |
| */ | |
| // @codeCoverageIgnoreStart | |
| require_once __DIR__ . '/WikimediaMaintenance.php'; | |
| // @codeCoverageIgnoreEnd | |
| use CirrusSearch\Maintenance\UpdateSearchIndexConfig; | |
| use MediaWiki\Installer\DatabaseCreator; | |
| use MediaWiki\Language\RawMessage; | |
| use MediaWiki\Mail\MailAddress; | |
| use MediaWiki\Mail\UserMailer; | |
| use MediaWiki\MainConfigNames; | |
| use MediaWiki\Maintenance\Maintenance; | |
| use MediaWiki\Status\Status; | |
| use MediaWiki\WikiMap\WikiMap; | |
| use Wikibase\Lib\Maintenance\PopulateSitesTable; | |
| class AddWiki extends InstallPreConfigured { | |
| public function __construct() { | |
| parent::__construct(); | |
| $this->addDescription( "Add a new wiki to the family. Wikimedia specific!" ); | |
| $this->addOption( 'allow-existing', | |
| 'Allow the script to run on an existing wiki' ); | |
| } | |
| /** @inheritDoc */ | |
| public function execute() { | |
| if ( $this->hasArg() ) { | |
| $this->fatalError( "This script no longer takes arguments. " . | |
| "Deploy the configuration first, and then use --wiki=<wiki-to-create>.\n" ); | |
| } | |
| $this->checkExistingWiki(); | |
| $this->checkLanguageName(); | |
| if ( !parent::execute() ) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| protected function getSubclassDefaultOptions(): array { | |
| global $wgConf; | |
| $options = []; | |
| $lang = $this->getConfig()->get( MainConfigNames::LanguageCode ); | |
| $options['LanguageNameInEnglish'] = $this->getServiceContainer()->getLanguageNameUtils() | |
| ->getLanguageName( $lang, 'en' ); | |
| global $wgSiteMatrixSites; | |
| [ $site, ] = $wgConf->siteFromDB( WikiMap::getCurrentWikiId() ); | |
| $options['SiteGroupInEnglish'] = $wgSiteMatrixSites[$site]['name'] | |
| ?? ucfirst( $site ?? 'wikipedia' ); | |
| // T415555: Always skip ALTER TABLE commands, as the database user used for install | |
| // does not have permission to execute those SQL statements. | |
| $options['SkipExtensionSchemaAlters'] = true; | |
| return $options; | |
| } | |
| protected function getExtraTaskSpecs(): array { | |
| return [ | |
| [ | |
| 'name' => 'populate-sites', | |
| 'description' => 'Populating the sites table on the new wiki', | |
| 'after' => 'extension-tables', | |
| 'callback' => function () { | |
| return $this->populateSites(); | |
| } | |
| ], | |
| [ | |
| 'name' => 'set-zone-access', | |
| 'description' => 'Configuring Swift zones', | |
| 'after' => 'extension-tables', | |
| 'callback' => function () { | |
| return $this->setZoneAccess(); | |
| } | |
| ], | |
| [ | |
| 'name' => 'search-index', | |
| 'description' => 'Configuring CirrusSearch indexes', | |
| 'after' => 'extension-tables', | |
| 'callback' => function () { | |
| return $this->updateSearchIndexConfig(); | |
| } | |
| ], | |
| [ | |
| 'name' => 'notify-newprojects', | |
| 'description' => 'Notifying the newprojects mailing list', | |
| 'postInstall' => true, | |
| 'callback' => function () { | |
| return $this->notifyNewProjects(); | |
| } | |
| ] | |
| ]; | |
| } | |
| protected function getTaskSkips(): array { | |
| return [ 'interwiki' ]; | |
| } | |
| /** | |
| * Check if the wiki already exists. This is in case of confusion with the old | |
| * addWiki.php which didn't act on the wiki specified with --wiki. | |
| */ | |
| private function checkExistingWiki() { | |
| $dbName = $this->getConfig()->get( MainConfigNames::DBname ); | |
| if ( !$this->hasOption( 'allow-existing' ) ) { | |
| $dbCreator = DatabaseCreator::createInstance( $this->getTaskContext() ); | |
| if ( $dbCreator->existsLocally( $dbName ) ) { | |
| $this->fatalError( "The wiki \"$dbName\" already exists. " . | |
| "Use --allow-existing to run installer tasks on an existing wiki." ); | |
| } | |
| } | |
| } | |
| /** | |
| * Check if the configured language code exists in the name list | |
| */ | |
| private function checkLanguageName() { | |
| $lang = $this->getConfig()->get( MainConfigNames::LanguageCode ); | |
| $languageNames = $this->getServiceContainer()->getLanguageNameUtils() | |
| ->getLanguageNames(); | |
| if ( !isset( $languageNames[$lang] ) ) { | |
| $this->fatalError( "Language $lang not found in Names.php" ); | |
| } | |
| } | |
| /** | |
| * Populate the sites table | |
| * | |
| * TODO: move to core. Move the weird bits to config. | |
| * | |
| * @return Status | |
| */ | |
| private function populateSites() { | |
| $extDir = $this->getConfig()->get( MainConfigNames::ExtensionDirectory ); | |
| // Populate sites table (this should be idempotent) | |
| // At least it's idempotent in the sense that it will give you the same fatal error every time | |
| return $this->runInstallScript( | |
| PopulateSitesTable::class, | |
| "$extDir/Wikibase/lib/maintenance/populateSitesTable.php", | |
| [ | |
| 'force-protocol' => 'https', | |
| ], | |
| ); | |
| } | |
| /** | |
| * Set up Swift zones | |
| * | |
| * TODO: move to core | |
| * | |
| * @return Status | |
| */ | |
| private function setZoneAccess() { | |
| $extDir = $this->getConfig()->get( MainConfigNames::ExtensionDirectory ); | |
| $options = [ | |
| 'backend' => 'local-multiwrite' | |
| ]; | |
| if ( $this->isPrivate() ) { | |
| $options['private'] = 1; | |
| } | |
| // Sets up the filebackend zones (this should be idempotent) | |
| return $this->runInstallScript( | |
| SetZoneAccess::class, | |
| "$extDir/WikimediaMaintenance/maintenance/filebackend/setZoneAccess.php", | |
| $options | |
| ); | |
| } | |
| /** | |
| * Set up ElasticSearch namespaces | |
| * | |
| * TODO: move to ElasticSearch install task or update | |
| * | |
| * @return Status | |
| */ | |
| private function updateSearchIndexConfig() { | |
| $extDir = $this->getConfig()->get( MainConfigNames::ExtensionDirectory ); | |
| return $this->runInstallScript( | |
| UpdateSearchIndexConfig::class, | |
| "$extDir/CirrusSearch/maintenance/UpdateSearchIndexConfig.php", | |
| [ 'cluster' => 'all' ] | |
| ); | |
| } | |
| /** | |
| * Send an email to the newprojects mailing list | |
| * | |
| * @return Status | |
| */ | |
| private function notifyNewProjects() { | |
| global $wmgAddWikiNotify, $wgConf; | |
| $config = $this->getConfig(); | |
| $wiki = WikiMap::getCurrentWikiId(); | |
| $to = new MailAddress( $wmgAddWikiNotify ); | |
| $from = new MailAddress( $config->get( MainConfigNames::PasswordSender ) ); | |
| $user = getenv( 'SUDO_USER' ); | |
| $time = wfTimestamp( TS_RFC2822 ); | |
| $lang = $config->get( MainConfigNames::LanguageCode ); | |
| [ $site, $prefix ] = $wgConf->siteFromDB( $wiki ); | |
| $url = $this->getServiceContainer()->getUrlUtils()->expand( '/' ); | |
| if ( $lang === $prefix ) { | |
| $langName = $this->getServiceContainer() | |
| ->getLanguageNameUtils() | |
| ->getLanguageName( $lang, 'en' ); | |
| $ucSiteGroup = $this->getTaskContext()->getOption( 'SiteGroupInEnglish' ); | |
| $body = "A new wiki was created by $user at $time for a $ucSiteGroup in $langName ($lang).\n" . | |
| "Once the wiki is fully set up, it'll be visible at $url"; | |
| } else { | |
| $siteName = $config->get( MainConfigNames::Sitename ); | |
| $body = "A new wiki was created by $user at $time for \"$siteName\".\n" . | |
| "Once the wiki is fully set up, it'll be visible at $url"; | |
| } | |
| return UserMailer::send( $to, $from, "New wiki: $wiki", $body ); | |
| } | |
| /** | |
| * Check if the wiki is private | |
| * | |
| * @return bool | |
| */ | |
| private function isPrivate() { | |
| return in_array( WikiMap::getCurrentWikiId(), MWWikiversions::readDbListFile( 'private' ) ); | |
| } | |
| /** | |
| * Run a maintenance script, with some informative messaging | |
| * | |
| * @param class-string<Maintenance> $class | |
| * @param string $classFile | |
| * @param array $options | |
| * @return Status | |
| */ | |
| private function runInstallScript( $class, $classFile, $options ) { | |
| $wiki = WikiMap::getCurrentWikiId(); | |
| $maint = $this->createChild( $class, $classFile ); | |
| $baseName = basename( $classFile ); | |
| $cmd = "$baseName --wiki=$wiki"; | |
| foreach ( $options as $name => $value ) { | |
| // escapeshellcmd looks nicer than escapeshellarg. This is just a | |
| // suggested command for the admin to paste, not derived from user input. | |
| // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.escapeshellcmd | |
| $cmd .= ' ' . escapeshellcmd( "--$name=$value" ); | |
| $maint->setOption( $name, $value ); | |
| } | |
| $this->output( "\nRunning maintenance script class as if executing: $cmd\n" ); | |
| if ( $maint->execute() !== false ) { | |
| return Status::newGood(); | |
| } else { | |
| return Status::newFatal( new RawMessage( "$baseName failed" ) ); | |
| } | |
| } | |
| } | |
| // @codeCoverageIgnoreStart | |
| $maintClass = AddWiki::class; | |
| require_once RUN_MAINTENANCE_IF_MAIN; | |
| // @codeCoverageIgnoreEnd |
US