Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 129 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
CliInstaller | |
0.00% |
0 / 129 |
|
0.00% |
0 / 14 |
1892 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 74 |
|
0.00% |
0 / 1 |
380 | |||
validateExtensions | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
execute | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
writeConfigurationFile | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
startStage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
endStage | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
showMessage | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
showError | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getMessageText | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
showHelpBox | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showStatusMessage | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
envCheckPath | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
envGetDefaultServer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
dirIsExecutable | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation; either version 2 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License along |
15 | * with this program; if not, write to the Free Software Foundation, Inc., |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * http://www.gnu.org/copyleft/gpl.html |
18 | * |
19 | * @file |
20 | * @ingroup Installer |
21 | */ |
22 | |
23 | namespace MediaWiki\Installer; |
24 | |
25 | use MediaWiki\Context\RequestContext; |
26 | use MediaWiki\MediaWikiServices; |
27 | use MediaWiki\Parser\Sanitizer; |
28 | use MediaWiki\Status\Status; |
29 | use MediaWiki\User\User; |
30 | use MessageSpecifier; |
31 | use UserPasswordPolicy; |
32 | |
33 | /** |
34 | * Class for the core installer command line interface. |
35 | * |
36 | * @ingroup Installer |
37 | * @since 1.17 |
38 | */ |
39 | class CliInstaller extends Installer { |
40 | private $specifiedScriptPath = false; |
41 | |
42 | private $optionMap = [ |
43 | 'dbtype' => 'wgDBtype', |
44 | 'dbserver' => 'wgDBserver', |
45 | 'dbname' => 'wgDBname', |
46 | 'dbuser' => 'wgDBuser', |
47 | 'dbpass' => 'wgDBpassword', |
48 | 'dbprefix' => 'wgDBprefix', |
49 | 'dbtableoptions' => 'wgDBTableOptions', |
50 | 'dbport' => 'wgDBport', |
51 | 'dbssl' => 'wgDBssl', |
52 | 'dbschema' => 'wgDBmwschema', |
53 | 'dbpath' => 'wgSQLiteDataDir', |
54 | 'server' => 'wgServer', |
55 | 'scriptpath' => 'wgScriptPath', |
56 | ]; |
57 | |
58 | /** |
59 | * @param string $siteName |
60 | * @param string|null $admin |
61 | * @param array $options |
62 | * @throws InstallException |
63 | */ |
64 | public function __construct( $siteName, $admin = null, array $options = [] ) { |
65 | global $wgPasswordPolicy; |
66 | |
67 | parent::__construct(); |
68 | |
69 | if ( isset( $options['scriptpath'] ) ) { |
70 | $this->specifiedScriptPath = true; |
71 | } |
72 | |
73 | foreach ( $this->optionMap as $opt => $global ) { |
74 | if ( isset( $options[$opt] ) ) { |
75 | $GLOBALS[$global] = $options[$opt]; |
76 | $this->setVar( $global, $options[$opt] ); |
77 | } |
78 | } |
79 | |
80 | if ( isset( $options['lang'] ) ) { |
81 | global $wgLang, $wgLanguageCode; |
82 | $this->setVar( '_UserLang', $options['lang'] ); |
83 | $wgLanguageCode = $options['lang']; |
84 | $this->setVar( 'wgLanguageCode', $wgLanguageCode ); |
85 | $wgLang = MediaWikiServices::getInstance()->getLanguageFactory() |
86 | ->getLanguage( $options['lang'] ); |
87 | RequestContext::getMain()->setLanguage( $wgLang ); |
88 | } |
89 | |
90 | $this->setVar( 'wgSitename', $siteName ); |
91 | |
92 | $contLang = MediaWikiServices::getInstance()->getContentLanguage(); |
93 | $metaNS = $contLang->ucfirst( str_replace( ' ', '_', $siteName ) ); |
94 | if ( $metaNS == 'MediaWiki' ) { |
95 | $metaNS = 'Project'; |
96 | } |
97 | $this->setVar( 'wgMetaNamespace', $metaNS ); |
98 | |
99 | if ( !isset( $options['installdbuser'] ) ) { |
100 | $this->setVar( '_InstallUser', |
101 | $this->getVar( 'wgDBuser' ) ); |
102 | $this->setVar( '_InstallPassword', |
103 | $this->getVar( 'wgDBpassword' ) ); |
104 | } else { |
105 | $this->setVar( '_InstallUser', |
106 | $options['installdbuser'] ); |
107 | $this->setVar( '_InstallPassword', |
108 | $options['installdbpass'] ?? "" ); |
109 | |
110 | // Assume that if we're given the installer user, we'll create the account. |
111 | $this->setVar( '_CreateDBAccount', true ); |
112 | } |
113 | |
114 | if ( $admin ) { |
115 | $this->setVar( '_AdminName', $admin ); |
116 | if ( isset( $options['pass'] ) ) { |
117 | $adminUser = User::newFromName( $admin ); |
118 | if ( !$adminUser ) { |
119 | throw new InstallException( Status::newFatal( 'config-admin-name-invalid' ) ); |
120 | } |
121 | $upp = new UserPasswordPolicy( |
122 | $wgPasswordPolicy['policies'], |
123 | $wgPasswordPolicy['checks'] |
124 | ); |
125 | $status = $upp->checkUserPasswordForGroups( $adminUser, $options['pass'], |
126 | [ 'bureaucrat', 'sysop', 'interface-admin' ] ); // per Installer::createSysop() |
127 | if ( !$status->isGood() ) { |
128 | throw new InstallException( Status::newFatal( |
129 | $status->getMessage( 'config-admin-error-password-invalid' ) ) ); |
130 | } |
131 | $this->setVar( '_AdminPassword', $options['pass'] ); |
132 | } |
133 | } |
134 | |
135 | // Detect and inject any extension found |
136 | if ( isset( $options['extensions'] ) ) { |
137 | $status = $this->validateExtensions( |
138 | 'extension', 'extensions', $options['extensions'] ); |
139 | if ( !$status->isOK() ) { |
140 | throw new InstallException( $status ); |
141 | } |
142 | $this->setVar( '_Extensions', $status->value ); |
143 | } elseif ( isset( $options['with-extensions'] ) ) { |
144 | $status = $this->findExtensions(); |
145 | if ( !$status->isOK() ) { |
146 | throw new InstallException( $status ); |
147 | } |
148 | $this->setVar( '_Extensions', array_keys( $status->value ) ); |
149 | } |
150 | |
151 | // Set up the default skins |
152 | if ( isset( $options['skins'] ) ) { |
153 | $status = $this->validateExtensions( 'skin', 'skins', $options['skins'] ); |
154 | if ( !$status->isOK() ) { |
155 | throw new InstallException( $status ); |
156 | } |
157 | $skins = $status->value; |
158 | } else { |
159 | $status = $this->findExtensions( 'skins' ); |
160 | if ( !$status->isOK() ) { |
161 | throw new InstallException( $status ); |
162 | } |
163 | $skins = array_keys( $status->value ); |
164 | } |
165 | $this->setVar( '_Skins', $skins ); |
166 | |
167 | if ( $skins ) { |
168 | $skinNames = array_map( 'strtolower', $skins ); |
169 | $this->setVar( 'wgDefaultSkin', $this->getDefaultSkin( $skinNames ) ); |
170 | } |
171 | |
172 | $this->setVar( '_WithDevelopmentSettings', isset( $options['with-developmentsettings'] ) ); |
173 | } |
174 | |
175 | private function validateExtensions( $type, $directory, $nameLists ) { |
176 | $extensions = []; |
177 | $status = new Status; |
178 | foreach ( (array)$nameLists as $nameList ) { |
179 | foreach ( explode( ',', $nameList ) as $name ) { |
180 | $name = trim( $name ); |
181 | if ( $name === '' ) { |
182 | continue; |
183 | } |
184 | $extStatus = $this->getExtensionInfo( $type, $directory, $name ); |
185 | if ( $extStatus->isOK() ) { |
186 | $extensions[] = $name; |
187 | } else { |
188 | $status->merge( $extStatus ); |
189 | } |
190 | } |
191 | } |
192 | $extensions = array_unique( $extensions ); |
193 | $status->value = $extensions; |
194 | return $status; |
195 | } |
196 | |
197 | /** |
198 | * Main entry point. |
199 | * @return Status |
200 | */ |
201 | public function execute() { |
202 | // If APC is available, use that as the MainCacheType, instead of nothing. |
203 | // This is hacky and should be consolidated with WebInstallerOptions. |
204 | // This is here instead of in __construct(), because it should run after |
205 | // doEnvironmentChecks(), which populates '_Caches'. |
206 | if ( count( $this->getVar( '_Caches' ) ) ) { |
207 | // We detected a CACHE_ACCEL implementation, use it. |
208 | $this->setVar( '_MainCacheType', 'accel' ); |
209 | } |
210 | |
211 | $vars = Installer::getExistingLocalSettings(); |
212 | if ( $vars ) { |
213 | $status = Status::newFatal( "config-localsettings-cli-upgrade" ); |
214 | $this->showStatusMessage( $status ); |
215 | return $status; |
216 | } |
217 | |
218 | $result = $this->performInstallation( |
219 | [ $this, 'startStage' ], |
220 | [ $this, 'endStage' ] |
221 | ); |
222 | // PerformInstallation bails on a fatal, so make sure the last item |
223 | // completed before giving 'next.' Likewise, only provide back on failure |
224 | $lastStepStatus = end( $result ); |
225 | if ( $lastStepStatus->isOK() ) { |
226 | return Status::newGood(); |
227 | } else { |
228 | return $lastStepStatus; |
229 | } |
230 | } |
231 | |
232 | /** |
233 | * Write LocalSettings.php to a given path |
234 | * |
235 | * @param string $path Full path to write LocalSettings.php to |
236 | */ |
237 | public function writeConfigurationFile( $path ) { |
238 | $ls = InstallerOverrides::getLocalSettingsGenerator( $this ); |
239 | $ls->writeFile( "$path/LocalSettings.php" ); |
240 | } |
241 | |
242 | public function startStage( $step ) { |
243 | // Messages: config-install-database, config-install-tables, config-install-interwiki, |
244 | // config-install-stats, config-install-keys, config-install-sysop, config-install-mainpage, |
245 | // config-install-extensions |
246 | $this->showMessage( "config-install-$step" ); |
247 | } |
248 | |
249 | public function endStage( $step, $status ) { |
250 | $this->showStatusMessage( $status ); |
251 | if ( $status->isOK() ) { |
252 | $this->showMessage( 'config-install-step-done' ); |
253 | } else { |
254 | $this->showError( 'config-install-step-failed' ); |
255 | } |
256 | } |
257 | |
258 | public function showMessage( $msg, ...$params ) { |
259 | // @phan-suppress-next-line SecurityCheck-XSS |
260 | echo $this->getMessageText( $msg, $params ) . "\n"; |
261 | flush(); |
262 | } |
263 | |
264 | public function showError( $msg, ...$params ) { |
265 | // @phan-suppress-next-line SecurityCheck-XSS |
266 | echo "***{$this->getMessageText( $msg, $params )}***\n"; |
267 | flush(); |
268 | } |
269 | |
270 | /** |
271 | * @param string|MessageSpecifier $msg |
272 | * @param array $params |
273 | * @return string |
274 | */ |
275 | protected function getMessageText( $msg, $params ) { |
276 | $text = wfMessage( $msg, $params )->parse(); |
277 | |
278 | $text = preg_replace( '/<a href="(.*?)".*?>(.*?)<\/a>/', '$2 <$1>', $text ); |
279 | |
280 | return Sanitizer::stripAllTags( $text ); |
281 | } |
282 | |
283 | /** |
284 | * Dummy |
285 | * @param string $msg Key for wfMessage() |
286 | * @param mixed ...$params |
287 | */ |
288 | public function showHelpBox( $msg, ...$params ) { |
289 | } |
290 | |
291 | public function showStatusMessage( Status $status ) { |
292 | $warnings = array_merge( $status->getWarningsArray(), |
293 | $status->getErrorsArray() ); |
294 | |
295 | if ( count( $warnings ) !== 0 ) { |
296 | foreach ( $warnings as $w ) { |
297 | $this->showMessage( ...$w ); |
298 | } |
299 | } |
300 | } |
301 | |
302 | public function envCheckPath() { |
303 | if ( !$this->specifiedScriptPath ) { |
304 | $this->showMessage( 'config-no-cli-uri', $this->getVar( "wgScriptPath" ) ); |
305 | } |
306 | |
307 | return parent::envCheckPath(); |
308 | } |
309 | |
310 | protected function envGetDefaultServer() { |
311 | // Use a basic value if the user didn't pass in --server |
312 | return 'http://localhost'; |
313 | } |
314 | |
315 | public function dirIsExecutable( $dir, $url ) { |
316 | $this->showMessage( 'config-no-cli-uploads-check', $dir ); |
317 | |
318 | return false; |
319 | } |
320 | } |