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