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