Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
InstallPreConfigured
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 8
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
2
 finalSetup
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 execute
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
 parseKeyValue
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 createTaskContext
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 createTaskList
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 createTaskRunner
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
20
 createTaskFactory
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Installer\Installer;
4use MediaWiki\Installer\Task\AddWikiTaskContext;
5use MediaWiki\Installer\Task\CannedProvider;
6use MediaWiki\Installer\Task\ITaskContext;
7use MediaWiki\Installer\Task\Task;
8use MediaWiki\Installer\Task\TaskFactory;
9use MediaWiki\Installer\Task\TaskList;
10use MediaWiki\Installer\Task\TaskRunner;
11use MediaWiki\MainConfigNames;
12use MediaWiki\Registration\ExtensionRegistry;
13use MediaWiki\Settings\SettingsBuilder;
14use Symfony\Component\Yaml\Yaml;
15
16require_once __DIR__ . '/Maintenance.php';
17
18class InstallPreConfigured extends Maintenance {
19    public function __construct() {
20        parent::__construct();
21        $this->addDescription( 'Create the database and tables for a new wiki, ' .
22            'using a pre-existing LocalSettings.php' );
23        $this->addOption( 'task',
24            'Execute only the specified task', false, true );
25        $this->addOption( 'skip',
26            'Skip the specified task', false, true, false, true );
27        $this->addOption( 'override-config',
28            'Specify a configuration variable with name=value. The value is in YAML format.',
29            false, true, 'c', true );
30        $this->addOption( 'override-option',
31            'Specify an installer option with name=value. The value is in YAML format.',
32            false, true, 'o', true );
33        $this->addOption( 'show-tasks',
34            'Show the list of tasks to be executed, do not actually install' );
35    }
36
37    public function finalSetup( SettingsBuilder $settingsBuilder ) {
38        parent::finalSetup( $settingsBuilder );
39
40        // Load installer i18n
41        $settingsBuilder->putConfigValue( MainConfigNames::MessagesDirs,
42            [ MW_INSTALL_PATH . '/includes/installer/i18n' ] );
43
44        // Apply override-config options. Doing this here instead of in
45        // AddWikiTaskContext::setConfigVar() allows the options to be available
46        // globally for use in LoadExtensionSchemaUpdates.
47        foreach ( $this->getOption( 'override-config' ) ?? [] as $str ) {
48            [ $name, $value ] = $this->parseKeyValue( $str );
49            if ( str_starts_with( $name, 'wg' ) ) {
50                $name = substr( $name, 2 );
51            }
52            $settingsBuilder->putConfigValue( $name, $value );
53        }
54    }
55
56    public function execute() {
57        $context = $this->createTaskContext();
58        $taskFactory = $this->createTaskFactory( $context );
59        $taskList = $this->createTaskList( $taskFactory );
60        $taskRunner = $this->createTaskRunner( $taskList, $taskFactory );
61
62        if ( $this->hasOption( 'show-tasks' ) ) {
63            $taskRunner->loadExtensions();
64            echo $taskRunner->dumpTaskList();
65            return true;
66        }
67
68        Installer::disableStorage( $this->getConfig(), 'en' );
69        if ( $this->hasOption( 'task' ) ) {
70            $status = $taskRunner->runNamedTask( $this->getOption( 'task' ) );
71        } else {
72            $status = $taskRunner->execute();
73        }
74
75        if ( $status->isOK() ) {
76            return true;
77        } else {
78            $this->error( "Installation failed at task \"" .
79                $taskRunner->getCurrentTaskName() . '"' );
80            return false;
81        }
82    }
83
84    /**
85     * Split a string into a key and a value, and parse the value as YAML.
86     *
87     * @param string $str
88     * @return array A list where the first element is the name, and the
89     *   second is the decoded value
90     */
91    private function parseKeyValue( string $str ) {
92        $parts = explode( '=', $str, 2 );
93        if ( count( $parts ) !== 2 ) {
94            $this->fatalError( "Invalid configuration variable \"$str\"" );
95        }
96        return [ $parts[0], Yaml::parse( $parts[1], Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE ) ];
97    }
98
99    /**
100     * Get the context for running tasks, with config overrides from the
101     * command line.
102     *
103     * @return AddWikiTaskContext
104     */
105    private function createTaskContext() {
106        $context = new AddWikiTaskContext(
107            $this->getConfig(),
108            $this->getServiceContainer()->getDBLoadBalancerFactory()
109        );
110
111        // Make sure ExtensionTablesTask isn't skipped
112        $context->setOption( 'Extensions',
113            array_keys( ExtensionRegistry::getInstance()->getAllThings() ) );
114
115        foreach ( $this->getOption( 'override-option' ) ?? [] as $str ) {
116            [ $name, $value ] = $this->parseKeyValue( $str );
117            $context->setOption( $name, $value );
118        }
119        return $context;
120    }
121
122    /**
123     * Get the full list of tasks, before skipping is applied.
124     *
125     * @param TaskFactory $taskFactory
126     * @return TaskList
127     */
128    private function createTaskList( TaskFactory $taskFactory ) {
129        $taskList = new TaskList;
130        $taskFactory->registerMainTasks( $taskList, TaskFactory::PROFILE_ADD_WIKI );
131        $reg = ExtensionRegistry::getInstance();
132        $taskList->add( $taskFactory->create(
133            [
134                'class' => CannedProvider::class,
135                'args' => [
136                    'extensions',
137                    [
138                        'HookContainer' => $this->getHookContainer(),
139                        'VirtualDomains' => $reg->getAttribute( 'DatabaseVirtualDomains' ),
140                        'ExtensionTaskSpecs' => $reg->getAttribute( 'InstallerTasks' ),
141                    ]
142                ]
143            ]
144        ) );
145        return $taskList;
146    }
147
148    /**
149     * Create and configure a TaskRunner
150     *
151     * @param TaskList $taskList
152     * @param TaskFactory $taskFactory
153     * @return TaskRunner
154     */
155    private function createTaskRunner( TaskList $taskList, TaskFactory $taskFactory ) {
156        $taskRunner = new TaskRunner( $taskList, $taskFactory, TaskFactory::PROFILE_ADD_WIKI );
157        $taskRunner->setSkippedTasks( $this->getOption( 'skip' ) ?? [] );
158
159        $taskRunner->addTaskStartListener( function ( Task $task ) {
160            $name = $task->getName();
161            $desc = $task->getDescriptionMessage()->plain();
162            $this->output( "[$name$desc... " );
163        } );
164
165        $taskRunner->addTaskEndListener( function ( $task, StatusValue $status ) {
166            if ( $status->isOK() ) {
167                $this->output( "done\n" );
168            } else {
169                $this->output( "\n" );
170            }
171            if ( !$status->isGood() ) {
172                try {
173                    $this->error( $status );
174                } catch ( InvalidArgumentException $e ) {
175                    $this->error( (string)$status );
176                }
177            }
178        } );
179
180        return $taskRunner;
181    }
182
183    /**
184     * Get the factory used to create tasks
185     *
186     * @param ITaskContext $context
187     * @return TaskFactory
188     */
189    private function createTaskFactory( ITaskContext $context ) {
190        return new TaskFactory(
191            $this->getServiceContainer()->getObjectFactory(),
192            $context
193        );
194    }
195}
196
197$maintClass = InstallPreConfigured::class;
198require_once RUN_MAINTENANCE_IF_MAIN;