These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Base code for MediaWiki installer. |
||
4 | * |
||
5 | * DO NOT PATCH THIS FILE IF YOU NEED TO CHANGE INSTALLER BEHAVIOR IN YOUR PACKAGE! |
||
6 | * See mw-config/overrides/README for details. |
||
7 | * |
||
8 | * This program is free software; you can redistribute it and/or modify |
||
9 | * it under the terms of the GNU General Public License as published by |
||
10 | * the Free Software Foundation; either version 2 of the License, or |
||
11 | * (at your option) any later version. |
||
12 | * |
||
13 | * This program is distributed in the hope that it will be useful, |
||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
16 | * GNU General Public License for more details. |
||
17 | * |
||
18 | * You should have received a copy of the GNU General Public License along |
||
19 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
21 | * http://www.gnu.org/copyleft/gpl.html |
||
22 | * |
||
23 | * @file |
||
24 | * @ingroup Deployment |
||
25 | */ |
||
26 | use MediaWiki\MediaWikiServices; |
||
27 | |||
28 | /** |
||
29 | * This documentation group collects source code files with deployment functionality. |
||
30 | * |
||
31 | * @defgroup Deployment Deployment |
||
32 | */ |
||
33 | |||
34 | /** |
||
35 | * Base installer class. |
||
36 | * |
||
37 | * This class provides the base for installation and update functionality |
||
38 | * for both MediaWiki core and extensions. |
||
39 | * |
||
40 | * @ingroup Deployment |
||
41 | * @since 1.17 |
||
42 | */ |
||
43 | abstract class Installer { |
||
44 | |||
45 | /** |
||
46 | * The oldest version of PCRE we can support. |
||
47 | * |
||
48 | * Defining this is necessary because PHP may be linked with a system version |
||
49 | * of PCRE, which may be older than that bundled with the minimum PHP version. |
||
50 | */ |
||
51 | const MINIMUM_PCRE_VERSION = '7.2'; |
||
52 | |||
53 | /** |
||
54 | * @var array |
||
55 | */ |
||
56 | protected $settings; |
||
57 | |||
58 | /** |
||
59 | * List of detected DBs, access using getCompiledDBs(). |
||
60 | * |
||
61 | * @var array |
||
62 | */ |
||
63 | protected $compiledDBs; |
||
64 | |||
65 | /** |
||
66 | * Cached DB installer instances, access using getDBInstaller(). |
||
67 | * |
||
68 | * @var array |
||
69 | */ |
||
70 | protected $dbInstallers = []; |
||
71 | |||
72 | /** |
||
73 | * Minimum memory size in MB. |
||
74 | * |
||
75 | * @var int |
||
76 | */ |
||
77 | protected $minMemorySize = 50; |
||
78 | |||
79 | /** |
||
80 | * Cached Title, used by parse(). |
||
81 | * |
||
82 | * @var Title |
||
83 | */ |
||
84 | protected $parserTitle; |
||
85 | |||
86 | /** |
||
87 | * Cached ParserOptions, used by parse(). |
||
88 | * |
||
89 | * @var ParserOptions |
||
90 | */ |
||
91 | protected $parserOptions; |
||
92 | |||
93 | /** |
||
94 | * Known database types. These correspond to the class names <type>Installer, |
||
95 | * and are also MediaWiki database types valid for $wgDBtype. |
||
96 | * |
||
97 | * To add a new type, create a <type>Installer class and a Database<type> |
||
98 | * class, and add a config-type-<type> message to MessagesEn.php. |
||
99 | * |
||
100 | * @var array |
||
101 | */ |
||
102 | protected static $dbTypes = [ |
||
103 | 'mysql', |
||
104 | 'postgres', |
||
105 | 'oracle', |
||
106 | 'mssql', |
||
107 | 'sqlite', |
||
108 | ]; |
||
109 | |||
110 | /** |
||
111 | * A list of environment check methods called by doEnvironmentChecks(). |
||
112 | * These may output warnings using showMessage(), and/or abort the |
||
113 | * installation process by returning false. |
||
114 | * |
||
115 | * For the WebInstaller these are only called on the Welcome page, |
||
116 | * if these methods have side-effects that should affect later page loads |
||
117 | * (as well as the generated stylesheet), use envPreps instead. |
||
118 | * |
||
119 | * @var array |
||
120 | */ |
||
121 | protected $envChecks = [ |
||
122 | 'envCheckDB', |
||
123 | 'envCheckBrokenXML', |
||
124 | 'envCheckPCRE', |
||
125 | 'envCheckMemory', |
||
126 | 'envCheckCache', |
||
127 | 'envCheckModSecurity', |
||
128 | 'envCheckDiff3', |
||
129 | 'envCheckGraphics', |
||
130 | 'envCheckGit', |
||
131 | 'envCheckServer', |
||
132 | 'envCheckPath', |
||
133 | 'envCheckShellLocale', |
||
134 | 'envCheckUploadsDirectory', |
||
135 | 'envCheckLibicu', |
||
136 | 'envCheckSuhosinMaxValueLength', |
||
137 | ]; |
||
138 | |||
139 | /** |
||
140 | * A list of environment preparation methods called by doEnvironmentPreps(). |
||
141 | * |
||
142 | * @var array |
||
143 | */ |
||
144 | protected $envPreps = [ |
||
145 | 'envPrepServer', |
||
146 | 'envPrepPath', |
||
147 | ]; |
||
148 | |||
149 | /** |
||
150 | * MediaWiki configuration globals that will eventually be passed through |
||
151 | * to LocalSettings.php. The names only are given here, the defaults |
||
152 | * typically come from DefaultSettings.php. |
||
153 | * |
||
154 | * @var array |
||
155 | */ |
||
156 | protected $defaultVarNames = [ |
||
157 | 'wgSitename', |
||
158 | 'wgPasswordSender', |
||
159 | 'wgLanguageCode', |
||
160 | 'wgRightsIcon', |
||
161 | 'wgRightsText', |
||
162 | 'wgRightsUrl', |
||
163 | 'wgEnableEmail', |
||
164 | 'wgEnableUserEmail', |
||
165 | 'wgEnotifUserTalk', |
||
166 | 'wgEnotifWatchlist', |
||
167 | 'wgEmailAuthentication', |
||
168 | 'wgDBname', |
||
169 | 'wgDBtype', |
||
170 | 'wgDiff3', |
||
171 | 'wgImageMagickConvertCommand', |
||
172 | 'wgGitBin', |
||
173 | 'IP', |
||
174 | 'wgScriptPath', |
||
175 | 'wgMetaNamespace', |
||
176 | 'wgDeletedDirectory', |
||
177 | 'wgEnableUploads', |
||
178 | 'wgShellLocale', |
||
179 | 'wgSecretKey', |
||
180 | 'wgUseInstantCommons', |
||
181 | 'wgUpgradeKey', |
||
182 | 'wgDefaultSkin', |
||
183 | 'wgPingback', |
||
184 | ]; |
||
185 | |||
186 | /** |
||
187 | * Variables that are stored alongside globals, and are used for any |
||
188 | * configuration of the installation process aside from the MediaWiki |
||
189 | * configuration. Map of names to defaults. |
||
190 | * |
||
191 | * @var array |
||
192 | */ |
||
193 | protected $internalDefaults = [ |
||
194 | '_UserLang' => 'en', |
||
195 | '_Environment' => false, |
||
196 | '_RaiseMemory' => false, |
||
197 | '_UpgradeDone' => false, |
||
198 | '_InstallDone' => false, |
||
199 | '_Caches' => [], |
||
200 | '_InstallPassword' => '', |
||
201 | '_SameAccount' => true, |
||
202 | '_CreateDBAccount' => false, |
||
203 | '_NamespaceType' => 'site-name', |
||
204 | '_AdminName' => '', // will be set later, when the user selects language |
||
205 | '_AdminPassword' => '', |
||
206 | '_AdminPasswordConfirm' => '', |
||
207 | '_AdminEmail' => '', |
||
208 | '_Subscribe' => false, |
||
209 | '_SkipOptional' => 'continue', |
||
210 | '_RightsProfile' => 'wiki', |
||
211 | '_LicenseCode' => 'none', |
||
212 | '_CCDone' => false, |
||
213 | '_Extensions' => [], |
||
214 | '_Skins' => [], |
||
215 | '_MemCachedServers' => '', |
||
216 | '_UpgradeKeySupplied' => false, |
||
217 | '_ExistingDBSettings' => false, |
||
218 | |||
219 | // $wgLogo is probably wrong (bug 48084); set something that will work. |
||
220 | // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped. |
||
221 | 'wgLogo' => '$wgResourceBasePath/resources/assets/wiki.png', |
||
222 | 'wgAuthenticationTokenVersion' => 1, |
||
223 | ]; |
||
224 | |||
225 | /** |
||
226 | * The actual list of installation steps. This will be initialized by getInstallSteps() |
||
227 | * |
||
228 | * @var array |
||
229 | */ |
||
230 | private $installSteps = []; |
||
231 | |||
232 | /** |
||
233 | * Extra steps for installation, for things like DatabaseInstallers to modify |
||
234 | * |
||
235 | * @var array |
||
236 | */ |
||
237 | protected $extraInstallSteps = []; |
||
238 | |||
239 | /** |
||
240 | * Known object cache types and the functions used to test for their existence. |
||
241 | * |
||
242 | * @var array |
||
243 | */ |
||
244 | protected $objectCaches = [ |
||
245 | 'xcache' => 'xcache_get', |
||
246 | 'apc' => 'apc_fetch', |
||
247 | 'apcu' => 'apcu_fetch', |
||
248 | 'wincache' => 'wincache_ucache_get' |
||
249 | ]; |
||
250 | |||
251 | /** |
||
252 | * User rights profiles. |
||
253 | * |
||
254 | * @var array |
||
255 | */ |
||
256 | public $rightsProfiles = [ |
||
257 | 'wiki' => [], |
||
258 | 'no-anon' => [ |
||
259 | '*' => [ 'edit' => false ] |
||
260 | ], |
||
261 | 'fishbowl' => [ |
||
262 | '*' => [ |
||
263 | 'createaccount' => false, |
||
264 | 'edit' => false, |
||
265 | ], |
||
266 | ], |
||
267 | 'private' => [ |
||
268 | '*' => [ |
||
269 | 'createaccount' => false, |
||
270 | 'edit' => false, |
||
271 | 'read' => false, |
||
272 | ], |
||
273 | ], |
||
274 | ]; |
||
275 | |||
276 | /** |
||
277 | * License types. |
||
278 | * |
||
279 | * @var array |
||
280 | */ |
||
281 | public $licenses = [ |
||
282 | 'cc-by' => [ |
||
283 | 'url' => 'https://creativecommons.org/licenses/by/4.0/', |
||
284 | 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by.png', |
||
285 | ], |
||
286 | 'cc-by-sa' => [ |
||
287 | 'url' => 'https://creativecommons.org/licenses/by-sa/4.0/', |
||
288 | 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-sa.png', |
||
289 | ], |
||
290 | 'cc-by-nc-sa' => [ |
||
291 | 'url' => 'https://creativecommons.org/licenses/by-nc-sa/4.0/', |
||
292 | 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-by-nc-sa.png', |
||
293 | ], |
||
294 | 'cc-0' => [ |
||
295 | 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/', |
||
296 | 'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png', |
||
297 | ], |
||
298 | 'gfdl' => [ |
||
299 | 'url' => 'https://www.gnu.org/copyleft/fdl.html', |
||
300 | 'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png', |
||
301 | ], |
||
302 | 'none' => [ |
||
303 | 'url' => '', |
||
304 | 'icon' => '', |
||
305 | 'text' => '' |
||
306 | ], |
||
307 | 'cc-choose' => [ |
||
308 | // Details will be filled in by the selector. |
||
309 | 'url' => '', |
||
310 | 'icon' => '', |
||
311 | 'text' => '', |
||
312 | ], |
||
313 | ]; |
||
314 | |||
315 | /** |
||
316 | * URL to mediawiki-announce subscription |
||
317 | */ |
||
318 | protected $mediaWikiAnnounceUrl = |
||
319 | 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce'; |
||
320 | |||
321 | /** |
||
322 | * Supported language codes for Mailman |
||
323 | */ |
||
324 | protected $mediaWikiAnnounceLanguages = [ |
||
325 | 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu', |
||
326 | 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', |
||
327 | 'sl', 'sr', 'sv', 'tr', 'uk' |
||
328 | ]; |
||
329 | |||
330 | /** |
||
331 | * UI interface for displaying a short message |
||
332 | * The parameters are like parameters to wfMessage(). |
||
333 | * The messages will be in wikitext format, which will be converted to an |
||
334 | * output format such as HTML or text before being sent to the user. |
||
335 | * @param string $msg |
||
336 | */ |
||
337 | abstract public function showMessage( $msg /*, ... */ ); |
||
338 | |||
339 | /** |
||
340 | * Same as showMessage(), but for displaying errors |
||
341 | * @param string $msg |
||
342 | */ |
||
343 | abstract public function showError( $msg /*, ... */ ); |
||
344 | |||
345 | /** |
||
346 | * Show a message to the installing user by using a Status object |
||
347 | * @param Status $status |
||
348 | */ |
||
349 | abstract public function showStatusMessage( Status $status ); |
||
350 | |||
351 | /** |
||
352 | * Constructs a Config object that contains configuration settings that should be |
||
353 | * overwritten for the installation process. |
||
354 | * |
||
355 | * @since 1.27 |
||
356 | * |
||
357 | * @param Config $baseConfig |
||
358 | * |
||
359 | * @return Config The config to use during installation. |
||
360 | */ |
||
361 | public static function getInstallerConfig( Config $baseConfig ) { |
||
362 | $configOverrides = new HashConfig(); |
||
363 | |||
364 | // disable (problematic) object cache types explicitly, preserving all other (working) ones |
||
365 | // bug T113843 |
||
366 | $emptyCache = [ 'class' => 'EmptyBagOStuff' ]; |
||
367 | |||
368 | $objectCaches = [ |
||
369 | CACHE_NONE => $emptyCache, |
||
370 | CACHE_DB => $emptyCache, |
||
371 | CACHE_ANYTHING => $emptyCache, |
||
372 | CACHE_MEMCACHED => $emptyCache, |
||
373 | ] + $baseConfig->get( 'ObjectCaches' ); |
||
374 | |||
375 | $configOverrides->set( 'ObjectCaches', $objectCaches ); |
||
376 | |||
377 | // Load the installer's i18n. |
||
378 | $messageDirs = $baseConfig->get( 'MessagesDirs' ); |
||
379 | $messageDirs['MediawikiInstaller'] = __DIR__ . '/i18n'; |
||
380 | |||
381 | $configOverrides->set( 'MessagesDirs', $messageDirs ); |
||
382 | |||
383 | $installerConfig = new MultiConfig( [ $configOverrides, $baseConfig ] ); |
||
384 | |||
385 | // make sure we use the installer config as the main config |
||
386 | $configRegistry = $baseConfig->get( 'ConfigRegistry' ); |
||
387 | $configRegistry['main'] = function() use ( $installerConfig ) { |
||
388 | return $installerConfig; |
||
389 | }; |
||
390 | |||
391 | $configOverrides->set( 'ConfigRegistry', $configRegistry ); |
||
392 | |||
393 | return $installerConfig; |
||
394 | } |
||
395 | |||
396 | /** |
||
397 | * Constructor, always call this from child classes. |
||
398 | */ |
||
399 | public function __construct() { |
||
400 | global $wgMemc, $wgUser, $wgObjectCaches; |
||
401 | |||
402 | $defaultConfig = new GlobalVarConfig(); // all the stuff from DefaultSettings.php |
||
403 | $installerConfig = self::getInstallerConfig( $defaultConfig ); |
||
404 | |||
405 | // Reset all services and inject config overrides |
||
406 | MediaWiki\MediaWikiServices::resetGlobalInstance( $installerConfig ); |
||
407 | |||
408 | // Don't attempt to load user language options (T126177) |
||
409 | // This will be overridden in the web installer with the user-specified language |
||
410 | RequestContext::getMain()->setLanguage( 'en' ); |
||
411 | |||
412 | // Disable the i18n cache |
||
413 | // TODO: manage LocalisationCache singleton in MediaWikiServices |
||
414 | Language::getLocalisationCache()->disableBackend(); |
||
415 | |||
416 | // Disable all global services, since we don't have any configuration yet! |
||
417 | MediaWiki\MediaWikiServices::disableStorageBackend(); |
||
418 | |||
419 | // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and |
||
420 | // SqlBagOStuff will then throw since we just disabled wfGetDB) |
||
421 | $wgObjectCaches = MediaWikiServices::getInstance()->getMainConfig()->get( 'ObjectCaches' ); |
||
422 | $wgMemc = ObjectCache::getInstance( CACHE_NONE ); |
||
423 | |||
424 | // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). |
||
425 | $wgUser = User::newFromId( 0 ); |
||
426 | RequestContext::getMain()->setUser( $wgUser ); |
||
427 | |||
428 | $this->settings = $this->internalDefaults; |
||
429 | |||
430 | foreach ( $this->defaultVarNames as $var ) { |
||
431 | $this->settings[$var] = $GLOBALS[$var]; |
||
432 | } |
||
433 | |||
434 | $this->doEnvironmentPreps(); |
||
435 | |||
436 | $this->compiledDBs = []; |
||
437 | foreach ( self::getDBTypes() as $type ) { |
||
438 | $installer = $this->getDBInstaller( $type ); |
||
439 | |||
440 | if ( !$installer->isCompiled() ) { |
||
441 | continue; |
||
442 | } |
||
443 | $this->compiledDBs[] = $type; |
||
444 | } |
||
445 | |||
446 | $this->parserTitle = Title::newFromText( 'Installer' ); |
||
447 | $this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :( |
||
448 | $this->parserOptions->setEditSection( false ); |
||
449 | } |
||
450 | |||
451 | /** |
||
452 | * Get a list of known DB types. |
||
453 | * |
||
454 | * @return array |
||
455 | */ |
||
456 | public static function getDBTypes() { |
||
457 | return self::$dbTypes; |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * Do initial checks of the PHP environment. Set variables according to |
||
462 | * the observed environment. |
||
463 | * |
||
464 | * It's possible that this may be called under the CLI SAPI, not the SAPI |
||
465 | * that the wiki will primarily run under. In that case, the subclass should |
||
466 | * initialise variables such as wgScriptPath, before calling this function. |
||
467 | * |
||
468 | * Under the web subclass, it can already be assumed that PHP 5+ is in use |
||
469 | * and that sessions are working. |
||
470 | * |
||
471 | * @return Status |
||
472 | */ |
||
473 | public function doEnvironmentChecks() { |
||
474 | // Php version has already been checked by entry scripts |
||
475 | // Show message here for information purposes |
||
476 | if ( wfIsHHVM() ) { |
||
477 | $this->showMessage( 'config-env-hhvm', HHVM_VERSION ); |
||
478 | } else { |
||
479 | $this->showMessage( 'config-env-php', PHP_VERSION ); |
||
480 | } |
||
481 | |||
482 | $good = true; |
||
483 | // Must go here because an old version of PCRE can prevent other checks from completing |
||
484 | list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 ); |
||
485 | if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) { |
||
486 | $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion ); |
||
487 | $good = false; |
||
488 | } else { |
||
489 | foreach ( $this->envChecks as $check ) { |
||
490 | $status = $this->$check(); |
||
491 | if ( $status === false ) { |
||
492 | $good = false; |
||
493 | } |
||
494 | } |
||
495 | } |
||
496 | |||
497 | $this->setVar( '_Environment', $good ); |
||
498 | |||
499 | return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' ); |
||
500 | } |
||
501 | |||
502 | public function doEnvironmentPreps() { |
||
503 | foreach ( $this->envPreps as $prep ) { |
||
504 | $this->$prep(); |
||
505 | } |
||
506 | } |
||
507 | |||
508 | /** |
||
509 | * Set a MW configuration variable, or internal installer configuration variable. |
||
510 | * |
||
511 | * @param string $name |
||
512 | * @param mixed $value |
||
513 | */ |
||
514 | public function setVar( $name, $value ) { |
||
515 | $this->settings[$name] = $value; |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * Get an MW configuration variable, or internal installer configuration variable. |
||
520 | * The defaults come from $GLOBALS (ultimately DefaultSettings.php). |
||
521 | * Installer variables are typically prefixed by an underscore. |
||
522 | * |
||
523 | * @param string $name |
||
524 | * @param mixed $default |
||
525 | * |
||
526 | * @return mixed |
||
527 | */ |
||
528 | View Code Duplication | public function getVar( $name, $default = null ) { |
|
529 | if ( !isset( $this->settings[$name] ) ) { |
||
530 | return $default; |
||
531 | } else { |
||
532 | return $this->settings[$name]; |
||
533 | } |
||
534 | } |
||
535 | |||
536 | /** |
||
537 | * Get a list of DBs supported by current PHP setup |
||
538 | * |
||
539 | * @return array |
||
540 | */ |
||
541 | public function getCompiledDBs() { |
||
542 | return $this->compiledDBs; |
||
543 | } |
||
544 | |||
545 | /** |
||
546 | * Get an instance of DatabaseInstaller for the specified DB type. |
||
547 | * |
||
548 | * @param mixed $type DB installer for which is needed, false to use default. |
||
549 | * |
||
550 | * @return DatabaseInstaller |
||
551 | */ |
||
552 | public function getDBInstaller( $type = false ) { |
||
553 | if ( !$type ) { |
||
554 | $type = $this->getVar( 'wgDBtype' ); |
||
555 | } |
||
556 | |||
557 | $type = strtolower( $type ); |
||
558 | |||
559 | if ( !isset( $this->dbInstallers[$type] ) ) { |
||
560 | $class = ucfirst( $type ) . 'Installer'; |
||
561 | $this->dbInstallers[$type] = new $class( $this ); |
||
562 | } |
||
563 | |||
564 | return $this->dbInstallers[$type]; |
||
565 | } |
||
566 | |||
567 | /** |
||
568 | * Determine if LocalSettings.php exists. If it does, return its variables. |
||
569 | * |
||
570 | * @return array |
||
571 | */ |
||
572 | public static function getExistingLocalSettings() { |
||
573 | global $IP; |
||
574 | |||
575 | // You might be wondering why this is here. Well if you don't do this |
||
576 | // then some poorly-formed extensions try to call their own classes |
||
577 | // after immediately registering them. We really need to get extension |
||
578 | // registration out of the global scope and into a real format. |
||
579 | // @see https://phabricator.wikimedia.org/T69440 |
||
580 | global $wgAutoloadClasses; |
||
581 | $wgAutoloadClasses = []; |
||
582 | |||
583 | // @codingStandardsIgnoreStart |
||
584 | // LocalSettings.php should not call functions, except wfLoadSkin/wfLoadExtensions |
||
585 | // Define the required globals here, to ensure, the functions can do it work correctly. |
||
586 | global $wgExtensionDirectory, $wgStyleDirectory; |
||
587 | // @codingStandardsIgnoreEnd |
||
588 | |||
589 | MediaWiki\suppressWarnings(); |
||
590 | $_lsExists = file_exists( "$IP/LocalSettings.php" ); |
||
591 | MediaWiki\restoreWarnings(); |
||
592 | |||
593 | if ( !$_lsExists ) { |
||
594 | return false; |
||
595 | } |
||
596 | unset( $_lsExists ); |
||
597 | |||
598 | require "$IP/includes/DefaultSettings.php"; |
||
599 | require "$IP/LocalSettings.php"; |
||
600 | |||
601 | return get_defined_vars(); |
||
602 | } |
||
603 | |||
604 | /** |
||
605 | * Get a fake password for sending back to the user in HTML. |
||
606 | * This is a security mechanism to avoid compromise of the password in the |
||
607 | * event of session ID compromise. |
||
608 | * |
||
609 | * @param string $realPassword |
||
610 | * |
||
611 | * @return string |
||
612 | */ |
||
613 | public function getFakePassword( $realPassword ) { |
||
614 | return str_repeat( '*', strlen( $realPassword ) ); |
||
615 | } |
||
616 | |||
617 | /** |
||
618 | * Set a variable which stores a password, except if the new value is a |
||
619 | * fake password in which case leave it as it is. |
||
620 | * |
||
621 | * @param string $name |
||
622 | * @param mixed $value |
||
623 | */ |
||
624 | public function setPassword( $name, $value ) { |
||
625 | if ( !preg_match( '/^\*+$/', $value ) ) { |
||
626 | $this->setVar( $name, $value ); |
||
627 | } |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * On POSIX systems return the primary group of the webserver we're running under. |
||
632 | * On other systems just returns null. |
||
633 | * |
||
634 | * This is used to advice the user that he should chgrp his mw-config/data/images directory as the |
||
635 | * webserver user before he can install. |
||
636 | * |
||
637 | * Public because SqliteInstaller needs it, and doesn't subclass Installer. |
||
638 | * |
||
639 | * @return mixed |
||
640 | */ |
||
641 | public static function maybeGetWebserverPrimaryGroup() { |
||
642 | if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { |
||
643 | # I don't know this, this isn't UNIX. |
||
644 | return null; |
||
645 | } |
||
646 | |||
647 | # posix_getegid() *not* getmygid() because we want the group of the webserver, |
||
648 | # not whoever owns the current script. |
||
649 | $gid = posix_getegid(); |
||
650 | $group = posix_getpwuid( $gid )['name']; |
||
651 | |||
652 | return $group; |
||
653 | } |
||
654 | |||
655 | /** |
||
656 | * Convert wikitext $text to HTML. |
||
657 | * |
||
658 | * This is potentially error prone since many parser features require a complete |
||
659 | * installed MW database. The solution is to just not use those features when you |
||
660 | * write your messages. This appears to work well enough. Basic formatting and |
||
661 | * external links work just fine. |
||
662 | * |
||
663 | * But in case a translator decides to throw in a "#ifexist" or internal link or |
||
664 | * whatever, this function is guarded to catch the attempted DB access and to present |
||
665 | * some fallback text. |
||
666 | * |
||
667 | * @param string $text |
||
668 | * @param bool $lineStart |
||
669 | * @return string |
||
670 | */ |
||
671 | public function parse( $text, $lineStart = false ) { |
||
672 | global $wgParser; |
||
673 | |||
674 | try { |
||
675 | $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart ); |
||
676 | $html = $out->getText(); |
||
677 | } catch ( DBAccessError $e ) { |
||
678 | $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text ); |
||
679 | |||
680 | if ( !empty( $this->debug ) ) { |
||
681 | $html .= "<!--\n" . $e->getTraceAsString() . "\n-->"; |
||
682 | } |
||
683 | } |
||
684 | |||
685 | return $html; |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * @return ParserOptions |
||
690 | */ |
||
691 | public function getParserOptions() { |
||
692 | return $this->parserOptions; |
||
693 | } |
||
694 | |||
695 | public function disableLinkPopups() { |
||
696 | $this->parserOptions->setExternalLinkTarget( false ); |
||
697 | } |
||
698 | |||
699 | public function restoreLinkPopups() { |
||
700 | global $wgExternalLinkTarget; |
||
701 | $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget ); |
||
702 | } |
||
703 | |||
704 | /** |
||
705 | * Install step which adds a row to the site_stats table with appropriate |
||
706 | * initial values. |
||
707 | * |
||
708 | * @param DatabaseInstaller $installer |
||
709 | * |
||
710 | * @return Status |
||
711 | */ |
||
712 | public function populateSiteStats( DatabaseInstaller $installer ) { |
||
713 | $status = $installer->getConnection(); |
||
714 | if ( !$status->isOK() ) { |
||
715 | return $status; |
||
716 | } |
||
717 | $status->value->insert( |
||
718 | 'site_stats', |
||
719 | [ |
||
720 | 'ss_row_id' => 1, |
||
721 | 'ss_total_edits' => 0, |
||
722 | 'ss_good_articles' => 0, |
||
723 | 'ss_total_pages' => 0, |
||
724 | 'ss_users' => 0, |
||
725 | 'ss_images' => 0 |
||
726 | ], |
||
727 | __METHOD__, 'IGNORE' |
||
728 | ); |
||
729 | |||
730 | return Status::newGood(); |
||
731 | } |
||
732 | |||
733 | /** |
||
734 | * Environment check for DB types. |
||
735 | * @return bool |
||
736 | */ |
||
737 | protected function envCheckDB() { |
||
738 | global $wgLang; |
||
739 | |||
740 | $allNames = []; |
||
741 | |||
742 | // Messages: config-type-mysql, config-type-postgres, config-type-oracle, |
||
743 | // config-type-sqlite |
||
744 | foreach ( self::getDBTypes() as $name ) { |
||
745 | $allNames[] = wfMessage( "config-type-$name" )->text(); |
||
746 | } |
||
747 | |||
748 | $databases = $this->getCompiledDBs(); |
||
749 | |||
750 | $databases = array_flip( $databases ); |
||
751 | foreach ( array_keys( $databases ) as $db ) { |
||
752 | $installer = $this->getDBInstaller( $db ); |
||
753 | $status = $installer->checkPrerequisites(); |
||
754 | if ( !$status->isGood() ) { |
||
755 | $this->showStatusMessage( $status ); |
||
756 | } |
||
757 | if ( !$status->isOK() ) { |
||
758 | unset( $databases[$db] ); |
||
759 | } |
||
760 | } |
||
761 | $databases = array_flip( $databases ); |
||
762 | if ( !$databases ) { |
||
763 | $this->showError( 'config-no-db', $wgLang->commaList( $allNames ), count( $allNames ) ); |
||
764 | |||
765 | // @todo FIXME: This only works for the web installer! |
||
766 | return false; |
||
767 | } |
||
768 | |||
769 | return true; |
||
770 | } |
||
771 | |||
772 | /** |
||
773 | * Some versions of libxml+PHP break < and > encoding horribly |
||
774 | * @return bool |
||
775 | */ |
||
776 | protected function envCheckBrokenXML() { |
||
777 | $test = new PhpXmlBugTester(); |
||
778 | if ( !$test->ok ) { |
||
779 | $this->showError( 'config-brokenlibxml' ); |
||
780 | |||
781 | return false; |
||
782 | } |
||
783 | |||
784 | return true; |
||
785 | } |
||
786 | |||
787 | /** |
||
788 | * Environment check for the PCRE module. |
||
789 | * |
||
790 | * @note If this check were to fail, the parser would |
||
791 | * probably throw an exception before the result |
||
792 | * of this check is shown to the user. |
||
793 | * @return bool |
||
794 | */ |
||
795 | protected function envCheckPCRE() { |
||
796 | MediaWiki\suppressWarnings(); |
||
797 | $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' ); |
||
798 | // Need to check for \p support too, as PCRE can be compiled |
||
799 | // with utf8 support, but not unicode property support. |
||
800 | // check that \p{Zs} (space separators) matches |
||
801 | // U+3000 (Ideographic space) |
||
802 | $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" ); |
||
803 | MediaWiki\restoreWarnings(); |
||
804 | if ( $regexd != '--' || $regexprop != '--' ) { |
||
805 | $this->showError( 'config-pcre-no-utf8' ); |
||
806 | |||
807 | return false; |
||
808 | } |
||
809 | |||
810 | return true; |
||
811 | } |
||
812 | |||
813 | /** |
||
814 | * Environment check for available memory. |
||
815 | * @return bool |
||
816 | */ |
||
817 | protected function envCheckMemory() { |
||
818 | $limit = ini_get( 'memory_limit' ); |
||
819 | |||
820 | if ( !$limit || $limit == -1 ) { |
||
821 | return true; |
||
822 | } |
||
823 | |||
824 | $n = wfShorthandToInteger( $limit ); |
||
825 | |||
826 | if ( $n < $this->minMemorySize * 1024 * 1024 ) { |
||
827 | $newLimit = "{$this->minMemorySize}M"; |
||
828 | |||
829 | if ( ini_set( "memory_limit", $newLimit ) === false ) { |
||
830 | $this->showMessage( 'config-memory-bad', $limit ); |
||
831 | } else { |
||
832 | $this->showMessage( 'config-memory-raised', $limit, $newLimit ); |
||
833 | $this->setVar( '_RaiseMemory', true ); |
||
834 | } |
||
835 | } |
||
836 | |||
837 | return true; |
||
838 | } |
||
839 | |||
840 | /** |
||
841 | * Environment check for compiled object cache types. |
||
842 | */ |
||
843 | protected function envCheckCache() { |
||
844 | $caches = []; |
||
845 | foreach ( $this->objectCaches as $name => $function ) { |
||
846 | if ( function_exists( $function ) ) { |
||
847 | if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) { |
||
848 | continue; |
||
849 | } |
||
850 | $caches[$name] = true; |
||
851 | } |
||
852 | } |
||
853 | |||
854 | if ( !$caches ) { |
||
855 | $key = 'config-no-cache-apcu'; |
||
856 | $this->showMessage( $key ); |
||
857 | } |
||
858 | |||
859 | $this->setVar( '_Caches', $caches ); |
||
860 | } |
||
861 | |||
862 | /** |
||
863 | * Scare user to death if they have mod_security or mod_security2 |
||
864 | * @return bool |
||
865 | */ |
||
866 | protected function envCheckModSecurity() { |
||
867 | if ( self::apacheModulePresent( 'mod_security' ) |
||
868 | || self::apacheModulePresent( 'mod_security2' ) ) { |
||
869 | $this->showMessage( 'config-mod-security' ); |
||
870 | } |
||
871 | |||
872 | return true; |
||
873 | } |
||
874 | |||
875 | /** |
||
876 | * Search for GNU diff3. |
||
877 | * @return bool |
||
878 | */ |
||
879 | protected function envCheckDiff3() { |
||
880 | $names = [ "gdiff3", "diff3", "diff3.exe" ]; |
||
881 | $versionInfo = [ '$1 --version 2>&1', 'GNU diffutils' ]; |
||
882 | |||
883 | $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo ); |
||
884 | |||
885 | if ( $diff3 ) { |
||
886 | $this->setVar( 'wgDiff3', $diff3 ); |
||
887 | } else { |
||
888 | $this->setVar( 'wgDiff3', false ); |
||
889 | $this->showMessage( 'config-diff3-bad' ); |
||
890 | } |
||
891 | |||
892 | return true; |
||
893 | } |
||
894 | |||
895 | /** |
||
896 | * Environment check for ImageMagick and GD. |
||
897 | * @return bool |
||
898 | */ |
||
899 | protected function envCheckGraphics() { |
||
900 | $names = [ wfIsWindows() ? 'convert.exe' : 'convert' ]; |
||
901 | $versionInfo = [ '$1 -version', 'ImageMagick' ]; |
||
902 | $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo ); |
||
903 | |||
904 | $this->setVar( 'wgImageMagickConvertCommand', '' ); |
||
905 | if ( $convert ) { |
||
906 | $this->setVar( 'wgImageMagickConvertCommand', $convert ); |
||
907 | $this->showMessage( 'config-imagemagick', $convert ); |
||
908 | |||
909 | return true; |
||
910 | } elseif ( function_exists( 'imagejpeg' ) ) { |
||
911 | $this->showMessage( 'config-gd' ); |
||
912 | } else { |
||
913 | $this->showMessage( 'config-no-scaling' ); |
||
914 | } |
||
915 | |||
916 | return true; |
||
917 | } |
||
918 | |||
919 | /** |
||
920 | * Search for git. |
||
921 | * |
||
922 | * @since 1.22 |
||
923 | * @return bool |
||
924 | */ |
||
925 | protected function envCheckGit() { |
||
926 | $names = [ wfIsWindows() ? 'git.exe' : 'git' ]; |
||
927 | $versionInfo = [ '$1 --version', 'git version' ]; |
||
928 | |||
929 | $git = self::locateExecutableInDefaultPaths( $names, $versionInfo ); |
||
930 | |||
931 | if ( $git ) { |
||
0 ignored issues
–
show
|
|||
932 | $this->setVar( 'wgGitBin', $git ); |
||
933 | $this->showMessage( 'config-git', $git ); |
||
934 | } else { |
||
935 | $this->setVar( 'wgGitBin', false ); |
||
936 | $this->showMessage( 'config-git-bad' ); |
||
937 | } |
||
938 | |||
939 | return true; |
||
940 | } |
||
941 | |||
942 | /** |
||
943 | * Environment check to inform user which server we've assumed. |
||
944 | * |
||
945 | * @return bool |
||
946 | */ |
||
947 | protected function envCheckServer() { |
||
948 | $server = $this->envGetDefaultServer(); |
||
949 | if ( $server !== null ) { |
||
950 | $this->showMessage( 'config-using-server', $server ); |
||
951 | } |
||
952 | return true; |
||
953 | } |
||
954 | |||
955 | /** |
||
956 | * Environment check to inform user which paths we've assumed. |
||
957 | * |
||
958 | * @return bool |
||
959 | */ |
||
960 | protected function envCheckPath() { |
||
961 | $this->showMessage( |
||
962 | 'config-using-uri', |
||
963 | $this->getVar( 'wgServer' ), |
||
964 | $this->getVar( 'wgScriptPath' ) |
||
965 | ); |
||
966 | return true; |
||
967 | } |
||
968 | |||
969 | /** |
||
970 | * Environment check for preferred locale in shell |
||
971 | * @return bool |
||
972 | */ |
||
973 | protected function envCheckShellLocale() { |
||
974 | $os = php_uname( 's' ); |
||
975 | $supported = [ 'Linux', 'SunOS', 'HP-UX', 'Darwin' ]; # Tested these |
||
976 | |||
977 | if ( !in_array( $os, $supported ) ) { |
||
978 | return true; |
||
979 | } |
||
980 | |||
981 | # Get a list of available locales. |
||
982 | $ret = false; |
||
983 | $lines = wfShellExec( '/usr/bin/locale -a', $ret ); |
||
984 | |||
985 | if ( $ret ) { |
||
986 | return true; |
||
987 | } |
||
988 | |||
989 | $lines = array_map( 'trim', explode( "\n", $lines ) ); |
||
990 | $candidatesByLocale = []; |
||
991 | $candidatesByLang = []; |
||
992 | |||
993 | foreach ( $lines as $line ) { |
||
994 | if ( $line === '' ) { |
||
995 | continue; |
||
996 | } |
||
997 | |||
998 | if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) { |
||
999 | continue; |
||
1000 | } |
||
1001 | |||
1002 | list( , $lang, , , ) = $m; |
||
1003 | |||
1004 | $candidatesByLocale[$m[0]] = $m; |
||
1005 | $candidatesByLang[$lang][] = $m; |
||
1006 | } |
||
1007 | |||
1008 | # Try the current value of LANG. |
||
1009 | if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) { |
||
1010 | $this->setVar( 'wgShellLocale', getenv( 'LANG' ) ); |
||
1011 | |||
1012 | return true; |
||
1013 | } |
||
1014 | |||
1015 | # Try the most common ones. |
||
1016 | $commonLocales = [ 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ]; |
||
1017 | foreach ( $commonLocales as $commonLocale ) { |
||
1018 | if ( isset( $candidatesByLocale[$commonLocale] ) ) { |
||
1019 | $this->setVar( 'wgShellLocale', $commonLocale ); |
||
1020 | |||
1021 | return true; |
||
1022 | } |
||
1023 | } |
||
1024 | |||
1025 | # Is there an available locale in the Wiki's language? |
||
1026 | $wikiLang = $this->getVar( 'wgLanguageCode' ); |
||
1027 | |||
1028 | if ( isset( $candidatesByLang[$wikiLang] ) ) { |
||
1029 | $m = reset( $candidatesByLang[$wikiLang] ); |
||
1030 | $this->setVar( 'wgShellLocale', $m[0] ); |
||
1031 | |||
1032 | return true; |
||
1033 | } |
||
1034 | |||
1035 | # Are there any at all? |
||
1036 | if ( count( $candidatesByLocale ) ) { |
||
1037 | $m = reset( $candidatesByLocale ); |
||
1038 | $this->setVar( 'wgShellLocale', $m[0] ); |
||
1039 | |||
1040 | return true; |
||
1041 | } |
||
1042 | |||
1043 | # Give up. |
||
1044 | return true; |
||
1045 | } |
||
1046 | |||
1047 | /** |
||
1048 | * Environment check for the permissions of the uploads directory |
||
1049 | * @return bool |
||
1050 | */ |
||
1051 | protected function envCheckUploadsDirectory() { |
||
1052 | global $IP; |
||
1053 | |||
1054 | $dir = $IP . '/images/'; |
||
1055 | $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/'; |
||
1056 | $safe = !$this->dirIsExecutable( $dir, $url ); |
||
1057 | |||
1058 | if ( !$safe ) { |
||
1059 | $this->showMessage( 'config-uploads-not-safe', $dir ); |
||
1060 | } |
||
1061 | |||
1062 | return true; |
||
1063 | } |
||
1064 | |||
1065 | /** |
||
1066 | * Checks if suhosin.get.max_value_length is set, and if so generate |
||
1067 | * a warning because it decreases ResourceLoader performance. |
||
1068 | * @return bool |
||
1069 | */ |
||
1070 | protected function envCheckSuhosinMaxValueLength() { |
||
1071 | $maxValueLength = ini_get( 'suhosin.get.max_value_length' ); |
||
1072 | if ( $maxValueLength > 0 && $maxValueLength < 1024 ) { |
||
1073 | // Only warn if the value is below the sane 1024 |
||
1074 | $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength ); |
||
1075 | } |
||
1076 | |||
1077 | return true; |
||
1078 | } |
||
1079 | |||
1080 | /** |
||
1081 | * Convert a hex string representing a Unicode code point to that code point. |
||
1082 | * @param string $c |
||
1083 | * @return string |
||
1084 | */ |
||
1085 | protected function unicodeChar( $c ) { |
||
1086 | $c = hexdec( $c ); |
||
1087 | if ( $c <= 0x7F ) { |
||
1088 | return chr( $c ); |
||
1089 | } elseif ( $c <= 0x7FF ) { |
||
1090 | return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F ); |
||
1091 | } elseif ( $c <= 0xFFFF ) { |
||
1092 | return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F ) . |
||
1093 | chr( 0x80 | $c & 0x3F ); |
||
1094 | } elseif ( $c <= 0x10FFFF ) { |
||
1095 | return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F ) . |
||
1096 | chr( 0x80 | $c >> 6 & 0x3F ) . |
||
1097 | chr( 0x80 | $c & 0x3F ); |
||
1098 | } else { |
||
1099 | return false; |
||
1100 | } |
||
1101 | } |
||
1102 | |||
1103 | /** |
||
1104 | * Check the libicu version |
||
1105 | */ |
||
1106 | protected function envCheckLibicu() { |
||
1107 | /** |
||
1108 | * This needs to be updated something that the latest libicu |
||
1109 | * will properly normalize. This normalization was found at |
||
1110 | * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions |
||
1111 | * Note that we use the hex representation to create the code |
||
1112 | * points in order to avoid any Unicode-destroying during transit. |
||
1113 | */ |
||
1114 | $not_normal_c = $this->unicodeChar( "FA6C" ); |
||
1115 | $normal_c = $this->unicodeChar( "242EE" ); |
||
1116 | |||
1117 | $useNormalizer = 'php'; |
||
1118 | $needsUpdate = false; |
||
1119 | |||
1120 | if ( function_exists( 'normalizer_normalize' ) ) { |
||
1121 | $useNormalizer = 'intl'; |
||
1122 | $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C ); |
||
1123 | if ( $intl !== $normal_c ) { |
||
1124 | $needsUpdate = true; |
||
1125 | } |
||
1126 | } |
||
1127 | |||
1128 | // Uses messages 'config-unicode-using-php' and 'config-unicode-using-intl' |
||
1129 | if ( $useNormalizer === 'php' ) { |
||
1130 | $this->showMessage( 'config-unicode-pure-php-warning' ); |
||
1131 | } else { |
||
1132 | $this->showMessage( 'config-unicode-using-' . $useNormalizer ); |
||
1133 | if ( $needsUpdate ) { |
||
1134 | $this->showMessage( 'config-unicode-update-warning' ); |
||
1135 | } |
||
1136 | } |
||
1137 | } |
||
1138 | |||
1139 | /** |
||
1140 | * Environment prep for the server hostname. |
||
1141 | */ |
||
1142 | protected function envPrepServer() { |
||
1143 | $server = $this->envGetDefaultServer(); |
||
1144 | if ( $server !== null ) { |
||
1145 | $this->setVar( 'wgServer', $server ); |
||
1146 | } |
||
1147 | } |
||
1148 | |||
1149 | /** |
||
1150 | * Helper function to be called from envPrepServer() |
||
1151 | * @return string |
||
1152 | */ |
||
1153 | abstract protected function envGetDefaultServer(); |
||
1154 | |||
1155 | /** |
||
1156 | * Environment prep for setting $IP and $wgScriptPath. |
||
1157 | */ |
||
1158 | protected function envPrepPath() { |
||
1159 | global $IP; |
||
1160 | $IP = dirname( dirname( __DIR__ ) ); |
||
1161 | $this->setVar( 'IP', $IP ); |
||
1162 | } |
||
1163 | |||
1164 | /** |
||
1165 | * Get an array of likely places we can find executables. Check a bunch |
||
1166 | * of known Unix-like defaults, as well as the PATH environment variable |
||
1167 | * (which should maybe make it work for Windows?) |
||
1168 | * |
||
1169 | * @return array |
||
1170 | */ |
||
1171 | protected static function getPossibleBinPaths() { |
||
1172 | return array_merge( |
||
1173 | [ '/usr/bin', '/usr/local/bin', '/opt/csw/bin', |
||
1174 | '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ], |
||
1175 | explode( PATH_SEPARATOR, getenv( 'PATH' ) ) |
||
1176 | ); |
||
1177 | } |
||
1178 | |||
1179 | /** |
||
1180 | * Search a path for any of the given executable names. Returns the |
||
1181 | * executable name if found. Also checks the version string returned |
||
1182 | * by each executable. |
||
1183 | * |
||
1184 | * Used only by environment checks. |
||
1185 | * |
||
1186 | * @param string $path Path to search |
||
1187 | * @param array $names Array of executable names |
||
1188 | * @param array|bool $versionInfo False or array with two members: |
||
1189 | * 0 => Command to run for version check, with $1 for the full executable name |
||
1190 | * 1 => String to compare the output with |
||
1191 | * |
||
1192 | * If $versionInfo is not false, only executables with a version |
||
1193 | * matching $versionInfo[1] will be returned. |
||
1194 | * @return bool|string |
||
1195 | */ |
||
1196 | public static function locateExecutable( $path, $names, $versionInfo = false ) { |
||
1197 | if ( !is_array( $names ) ) { |
||
1198 | $names = [ $names ]; |
||
1199 | } |
||
1200 | |||
1201 | foreach ( $names as $name ) { |
||
1202 | $command = $path . DIRECTORY_SEPARATOR . $name; |
||
1203 | |||
1204 | MediaWiki\suppressWarnings(); |
||
1205 | $file_exists = is_executable( $command ); |
||
1206 | MediaWiki\restoreWarnings(); |
||
1207 | |||
1208 | if ( $file_exists ) { |
||
1209 | if ( !$versionInfo ) { |
||
1210 | return $command; |
||
1211 | } |
||
1212 | |||
1213 | $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] ); |
||
1214 | if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) { |
||
1215 | return $command; |
||
1216 | } |
||
1217 | } |
||
1218 | } |
||
1219 | |||
1220 | return false; |
||
1221 | } |
||
1222 | |||
1223 | /** |
||
1224 | * Same as locateExecutable(), but checks in getPossibleBinPaths() by default |
||
1225 | * @see locateExecutable() |
||
1226 | * @param array $names Array of possible names. |
||
1227 | * @param array|bool $versionInfo Default: false or array with two members: |
||
1228 | * 0 => Command to run for version check, with $1 for the full executable name |
||
1229 | * 1 => String to compare the output with |
||
1230 | * |
||
1231 | * If $versionInfo is not false, only executables with a version |
||
1232 | * matching $versionInfo[1] will be returned. |
||
1233 | * @return bool|string |
||
1234 | */ |
||
1235 | public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) { |
||
1236 | foreach ( self::getPossibleBinPaths() as $path ) { |
||
1237 | $exe = self::locateExecutable( $path, $names, $versionInfo ); |
||
1238 | if ( $exe !== false ) { |
||
1239 | return $exe; |
||
1240 | } |
||
1241 | } |
||
1242 | |||
1243 | return false; |
||
1244 | } |
||
1245 | |||
1246 | /** |
||
1247 | * Checks if scripts located in the given directory can be executed via the given URL. |
||
1248 | * |
||
1249 | * Used only by environment checks. |
||
1250 | * @param string $dir |
||
1251 | * @param string $url |
||
1252 | * @return bool|int|string |
||
1253 | */ |
||
1254 | public function dirIsExecutable( $dir, $url ) { |
||
1255 | $scriptTypes = [ |
||
1256 | 'php' => [ |
||
1257 | "<?php echo 'ex' . 'ec';", |
||
1258 | "#!/var/env php5\n<?php echo 'ex' . 'ec';", |
||
1259 | ], |
||
1260 | ]; |
||
1261 | |||
1262 | // it would be good to check other popular languages here, but it'll be slow. |
||
1263 | |||
1264 | MediaWiki\suppressWarnings(); |
||
1265 | |||
1266 | foreach ( $scriptTypes as $ext => $contents ) { |
||
1267 | foreach ( $contents as $source ) { |
||
1268 | $file = 'exectest.' . $ext; |
||
1269 | |||
1270 | if ( !file_put_contents( $dir . $file, $source ) ) { |
||
1271 | break; |
||
1272 | } |
||
1273 | |||
1274 | try { |
||
1275 | $text = Http::get( $url . $file, [ 'timeout' => 3 ], __METHOD__ ); |
||
1276 | } catch ( Exception $e ) { |
||
1277 | // Http::get throws with allow_url_fopen = false and no curl extension. |
||
1278 | $text = null; |
||
1279 | } |
||
1280 | unlink( $dir . $file ); |
||
1281 | |||
1282 | if ( $text == 'exec' ) { |
||
1283 | MediaWiki\restoreWarnings(); |
||
1284 | |||
1285 | return $ext; |
||
1286 | } |
||
1287 | } |
||
1288 | } |
||
1289 | |||
1290 | MediaWiki\restoreWarnings(); |
||
1291 | |||
1292 | return false; |
||
1293 | } |
||
1294 | |||
1295 | /** |
||
1296 | * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too. |
||
1297 | * |
||
1298 | * @param string $moduleName Name of module to check. |
||
1299 | * @return bool |
||
1300 | */ |
||
1301 | public static function apacheModulePresent( $moduleName ) { |
||
1302 | if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) { |
||
1303 | return true; |
||
1304 | } |
||
1305 | // try it the hard way |
||
1306 | ob_start(); |
||
1307 | phpinfo( INFO_MODULES ); |
||
1308 | $info = ob_get_clean(); |
||
1309 | |||
1310 | return strpos( $info, $moduleName ) !== false; |
||
1311 | } |
||
1312 | |||
1313 | /** |
||
1314 | * ParserOptions are constructed before we determined the language, so fix it |
||
1315 | * |
||
1316 | * @param Language $lang |
||
1317 | */ |
||
1318 | public function setParserLanguage( $lang ) { |
||
1319 | $this->parserOptions->setTargetLanguage( $lang ); |
||
1320 | $this->parserOptions->setUserLang( $lang ); |
||
1321 | } |
||
1322 | |||
1323 | /** |
||
1324 | * Overridden by WebInstaller to provide lastPage parameters. |
||
1325 | * @param string $page |
||
1326 | * @return string |
||
1327 | */ |
||
1328 | protected function getDocUrl( $page ) { |
||
1329 | return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); |
||
1330 | } |
||
1331 | |||
1332 | /** |
||
1333 | * Finds extensions that follow the format /$directory/Name/Name.php, |
||
1334 | * and returns an array containing the value for 'Name' for each found extension. |
||
1335 | * |
||
1336 | * Reasonable values for $directory include 'extensions' (the default) and 'skins'. |
||
1337 | * |
||
1338 | * @param string $directory Directory to search in |
||
1339 | * @return array |
||
1340 | */ |
||
1341 | public function findExtensions( $directory = 'extensions' ) { |
||
1342 | if ( $this->getVar( 'IP' ) === null ) { |
||
1343 | return []; |
||
1344 | } |
||
1345 | |||
1346 | $extDir = $this->getVar( 'IP' ) . '/' . $directory; |
||
1347 | if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) { |
||
1348 | return []; |
||
1349 | } |
||
1350 | |||
1351 | // extensions -> extension.json, skins -> skin.json |
||
1352 | $jsonFile = substr( $directory, 0, strlen( $directory ) -1 ) . '.json'; |
||
1353 | |||
1354 | $dh = opendir( $extDir ); |
||
1355 | $exts = []; |
||
1356 | while ( ( $file = readdir( $dh ) ) !== false ) { |
||
1357 | if ( !is_dir( "$extDir/$file" ) ) { |
||
1358 | continue; |
||
1359 | } |
||
1360 | if ( file_exists( "$extDir/$file/$jsonFile" ) || file_exists( "$extDir/$file/$file.php" ) ) { |
||
1361 | $exts[] = $file; |
||
1362 | } |
||
1363 | } |
||
1364 | closedir( $dh ); |
||
1365 | natcasesort( $exts ); |
||
1366 | |||
1367 | return $exts; |
||
1368 | } |
||
1369 | |||
1370 | /** |
||
1371 | * Returns a default value to be used for $wgDefaultSkin: normally the one set in DefaultSettings, |
||
1372 | * but will fall back to another if the default skin is missing and some other one is present |
||
1373 | * instead. |
||
1374 | * |
||
1375 | * @param string[] $skinNames Names of installed skins. |
||
1376 | * @return string |
||
1377 | */ |
||
1378 | public function getDefaultSkin( array $skinNames ) { |
||
1379 | $defaultSkin = $GLOBALS['wgDefaultSkin']; |
||
1380 | if ( !$skinNames || in_array( $defaultSkin, $skinNames ) ) { |
||
1381 | return $defaultSkin; |
||
1382 | } else { |
||
1383 | return $skinNames[0]; |
||
1384 | } |
||
1385 | } |
||
1386 | |||
1387 | /** |
||
1388 | * Installs the auto-detected extensions. |
||
1389 | * |
||
1390 | * @return Status |
||
1391 | */ |
||
1392 | protected function includeExtensions() { |
||
1393 | global $IP; |
||
1394 | $exts = $this->getVar( '_Extensions' ); |
||
1395 | $IP = $this->getVar( 'IP' ); |
||
1396 | |||
1397 | /** |
||
1398 | * We need to include DefaultSettings before including extensions to avoid |
||
1399 | * warnings about unset variables. However, the only thing we really |
||
1400 | * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work |
||
1401 | * if the extension has hidden hook registration in $wgExtensionFunctions, |
||
1402 | * but we're not opening that can of worms |
||
1403 | * @see https://phabricator.wikimedia.org/T28857 |
||
1404 | */ |
||
1405 | global $wgAutoloadClasses; |
||
1406 | $wgAutoloadClasses = []; |
||
1407 | $queue = []; |
||
1408 | |||
1409 | require "$IP/includes/DefaultSettings.php"; |
||
1410 | |||
1411 | foreach ( $exts as $e ) { |
||
1412 | if ( file_exists( "$IP/extensions/$e/extension.json" ) ) { |
||
1413 | $queue["$IP/extensions/$e/extension.json"] = 1; |
||
1414 | } else { |
||
1415 | require_once "$IP/extensions/$e/$e.php"; |
||
1416 | } |
||
1417 | } |
||
1418 | |||
1419 | $registry = new ExtensionRegistry(); |
||
1420 | $data = $registry->readFromQueue( $queue ); |
||
1421 | $wgAutoloadClasses += $data['autoload']; |
||
1422 | |||
1423 | $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ? |
||
1424 | $wgHooks['LoadExtensionSchemaUpdates'] : []; |
||
1425 | |||
1426 | View Code Duplication | if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { |
|
1427 | $hooksWeWant = array_merge_recursive( |
||
1428 | $hooksWeWant, |
||
1429 | $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] |
||
1430 | ); |
||
1431 | } |
||
1432 | // Unset everyone else's hooks. Lord knows what someone might be doing |
||
1433 | // in ParserFirstCallInit (see bug 27171) |
||
1434 | $GLOBALS['wgHooks'] = [ 'LoadExtensionSchemaUpdates' => $hooksWeWant ]; |
||
1435 | |||
1436 | return Status::newGood(); |
||
1437 | } |
||
1438 | |||
1439 | /** |
||
1440 | * Get an array of install steps. Should always be in the format of |
||
1441 | * [ |
||
1442 | * 'name' => 'someuniquename', |
||
1443 | * 'callback' => [ $obj, 'method' ], |
||
1444 | * ] |
||
1445 | * There must be a config-install-$name message defined per step, which will |
||
1446 | * be shown on install. |
||
1447 | * |
||
1448 | * @param DatabaseInstaller $installer DatabaseInstaller so we can make callbacks |
||
1449 | * @return array |
||
1450 | */ |
||
1451 | protected function getInstallSteps( DatabaseInstaller $installer ) { |
||
1452 | $coreInstallSteps = [ |
||
1453 | [ 'name' => 'database', 'callback' => [ $installer, 'setupDatabase' ] ], |
||
1454 | [ 'name' => 'tables', 'callback' => [ $installer, 'createTables' ] ], |
||
1455 | [ 'name' => 'interwiki', 'callback' => [ $installer, 'populateInterwikiTable' ] ], |
||
1456 | [ 'name' => 'stats', 'callback' => [ $this, 'populateSiteStats' ] ], |
||
1457 | [ 'name' => 'keys', 'callback' => [ $this, 'generateKeys' ] ], |
||
1458 | [ 'name' => 'updates', 'callback' => [ $installer, 'insertUpdateKeys' ] ], |
||
1459 | [ 'name' => 'sysop', 'callback' => [ $this, 'createSysop' ] ], |
||
1460 | [ 'name' => 'mainpage', 'callback' => [ $this, 'createMainpage' ] ], |
||
1461 | ]; |
||
1462 | |||
1463 | // Build the array of install steps starting from the core install list, |
||
1464 | // then adding any callbacks that wanted to attach after a given step |
||
1465 | foreach ( $coreInstallSteps as $step ) { |
||
1466 | $this->installSteps[] = $step; |
||
1467 | View Code Duplication | if ( isset( $this->extraInstallSteps[$step['name']] ) ) { |
|
1468 | $this->installSteps = array_merge( |
||
1469 | $this->installSteps, |
||
1470 | $this->extraInstallSteps[$step['name']] |
||
1471 | ); |
||
1472 | } |
||
1473 | } |
||
1474 | |||
1475 | // Prepend any steps that want to be at the beginning |
||
1476 | View Code Duplication | if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) { |
|
1477 | $this->installSteps = array_merge( |
||
1478 | $this->extraInstallSteps['BEGINNING'], |
||
1479 | $this->installSteps |
||
1480 | ); |
||
1481 | } |
||
1482 | |||
1483 | // Extensions should always go first, chance to tie into hooks and such |
||
1484 | if ( count( $this->getVar( '_Extensions' ) ) ) { |
||
1485 | array_unshift( $this->installSteps, |
||
1486 | [ 'name' => 'extensions', 'callback' => [ $this, 'includeExtensions' ] ] |
||
1487 | ); |
||
1488 | $this->installSteps[] = [ |
||
1489 | 'name' => 'extension-tables', |
||
1490 | 'callback' => [ $installer, 'createExtensionTables' ] |
||
1491 | ]; |
||
1492 | } |
||
1493 | |||
1494 | return $this->installSteps; |
||
1495 | } |
||
1496 | |||
1497 | /** |
||
1498 | * Actually perform the installation. |
||
1499 | * |
||
1500 | * @param callable $startCB A callback array for the beginning of each step |
||
1501 | * @param callable $endCB A callback array for the end of each step |
||
1502 | * |
||
1503 | * @return array Array of Status objects |
||
1504 | */ |
||
1505 | public function performInstallation( $startCB, $endCB ) { |
||
1506 | $installResults = []; |
||
1507 | $installer = $this->getDBInstaller(); |
||
1508 | $installer->preInstall(); |
||
1509 | $steps = $this->getInstallSteps( $installer ); |
||
1510 | foreach ( $steps as $stepObj ) { |
||
1511 | $name = $stepObj['name']; |
||
1512 | call_user_func_array( $startCB, [ $name ] ); |
||
1513 | |||
1514 | // Perform the callback step |
||
1515 | $status = call_user_func( $stepObj['callback'], $installer ); |
||
1516 | |||
1517 | // Output and save the results |
||
1518 | call_user_func( $endCB, $name, $status ); |
||
1519 | $installResults[$name] = $status; |
||
1520 | |||
1521 | // If we've hit some sort of fatal, we need to bail. |
||
1522 | // Callback already had a chance to do output above. |
||
1523 | if ( !$status->isOk() ) { |
||
1524 | break; |
||
1525 | } |
||
1526 | } |
||
1527 | if ( $status->isOk() ) { |
||
1528 | $this->setVar( '_InstallDone', true ); |
||
1529 | } |
||
1530 | |||
1531 | return $installResults; |
||
1532 | } |
||
1533 | |||
1534 | /** |
||
1535 | * Generate $wgSecretKey. Will warn if we had to use an insecure random source. |
||
1536 | * |
||
1537 | * @return Status |
||
1538 | */ |
||
1539 | public function generateKeys() { |
||
1540 | $keys = [ 'wgSecretKey' => 64 ]; |
||
1541 | if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { |
||
1542 | $keys['wgUpgradeKey'] = 16; |
||
1543 | } |
||
1544 | |||
1545 | return $this->doGenerateKeys( $keys ); |
||
1546 | } |
||
1547 | |||
1548 | /** |
||
1549 | * Generate a secret value for variables using our CryptRand generator. |
||
1550 | * Produce a warning if the random source was insecure. |
||
1551 | * |
||
1552 | * @param array $keys |
||
1553 | * @return Status |
||
1554 | */ |
||
1555 | protected function doGenerateKeys( $keys ) { |
||
1556 | $status = Status::newGood(); |
||
1557 | |||
1558 | $strong = true; |
||
1559 | foreach ( $keys as $name => $length ) { |
||
1560 | $secretKey = MWCryptRand::generateHex( $length, true ); |
||
1561 | if ( !MWCryptRand::wasStrong() ) { |
||
1562 | $strong = false; |
||
1563 | } |
||
1564 | |||
1565 | $this->setVar( $name, $secretKey ); |
||
1566 | } |
||
1567 | |||
1568 | if ( !$strong ) { |
||
1569 | $names = array_keys( $keys ); |
||
1570 | $names = preg_replace( '/^(.*)$/', '\$$1', $names ); |
||
1571 | global $wgLang; |
||
1572 | $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); |
||
1573 | } |
||
1574 | |||
1575 | return $status; |
||
1576 | } |
||
1577 | |||
1578 | /** |
||
1579 | * Create the first user account, grant it sysop and bureaucrat rights |
||
1580 | * |
||
1581 | * @return Status |
||
1582 | */ |
||
1583 | protected function createSysop() { |
||
1584 | $name = $this->getVar( '_AdminName' ); |
||
1585 | $user = User::newFromName( $name ); |
||
1586 | |||
1587 | if ( !$user ) { |
||
1588 | // We should've validated this earlier anyway! |
||
1589 | return Status::newFatal( 'config-admin-error-user', $name ); |
||
1590 | } |
||
1591 | |||
1592 | if ( $user->idForName() == 0 ) { |
||
1593 | $user->addToDatabase(); |
||
1594 | |||
1595 | try { |
||
1596 | $user->setPassword( $this->getVar( '_AdminPassword' ) ); |
||
1597 | } catch ( PasswordError $pwe ) { |
||
1598 | return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); |
||
1599 | } |
||
1600 | |||
1601 | $user->addGroup( 'sysop' ); |
||
1602 | $user->addGroup( 'bureaucrat' ); |
||
1603 | if ( $this->getVar( '_AdminEmail' ) ) { |
||
1604 | $user->setEmail( $this->getVar( '_AdminEmail' ) ); |
||
1605 | } |
||
1606 | $user->saveSettings(); |
||
1607 | |||
1608 | // Update user count |
||
1609 | $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); |
||
1610 | $ssUpdate->doUpdate(); |
||
1611 | } |
||
1612 | $status = Status::newGood(); |
||
1613 | |||
1614 | if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { |
||
1615 | $this->subscribeToMediaWikiAnnounce( $status ); |
||
1616 | } |
||
1617 | |||
1618 | return $status; |
||
1619 | } |
||
1620 | |||
1621 | /** |
||
1622 | * @param Status $s |
||
1623 | */ |
||
1624 | private function subscribeToMediaWikiAnnounce( Status $s ) { |
||
1625 | $params = [ |
||
1626 | 'email' => $this->getVar( '_AdminEmail' ), |
||
1627 | 'language' => 'en', |
||
1628 | 'digest' => 0 |
||
1629 | ]; |
||
1630 | |||
1631 | // Mailman doesn't support as many languages as we do, so check to make |
||
1632 | // sure their selected language is available |
||
1633 | $myLang = $this->getVar( '_UserLang' ); |
||
1634 | if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) { |
||
1635 | $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR |
||
1636 | $params['language'] = $myLang; |
||
1637 | } |
||
1638 | |||
1639 | if ( MWHttpRequest::canMakeRequests() ) { |
||
1640 | $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl, |
||
1641 | [ 'method' => 'POST', 'postData' => $params ], __METHOD__ )->execute(); |
||
1642 | if ( !$res->isOK() ) { |
||
1643 | $s->warning( 'config-install-subscribe-fail', $res->getMessage() ); |
||
1644 | } |
||
1645 | } else { |
||
1646 | $s->warning( 'config-install-subscribe-notpossible' ); |
||
1647 | } |
||
1648 | } |
||
1649 | |||
1650 | /** |
||
1651 | * Insert Main Page with default content. |
||
1652 | * |
||
1653 | * @param DatabaseInstaller $installer |
||
1654 | * @return Status |
||
1655 | */ |
||
1656 | protected function createMainpage( DatabaseInstaller $installer ) { |
||
1657 | $status = Status::newGood(); |
||
1658 | try { |
||
1659 | $page = WikiPage::factory( Title::newMainPage() ); |
||
1660 | $content = new WikitextContent( |
||
1661 | wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" . |
||
1662 | wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text() |
||
1663 | ); |
||
1664 | |||
1665 | $status = $page->doEditContent( $content, |
||
1666 | '', |
||
1667 | EDIT_NEW, |
||
1668 | false, |
||
1669 | User::newFromName( 'MediaWiki default' ) |
||
1670 | ); |
||
1671 | } catch ( Exception $e ) { |
||
1672 | // using raw, because $wgShowExceptionDetails can not be set yet |
||
1673 | $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); |
||
1674 | } |
||
1675 | |||
1676 | return $status; |
||
1677 | } |
||
1678 | |||
1679 | /** |
||
1680 | * Override the necessary bits of the config to run an installation. |
||
1681 | */ |
||
1682 | public static function overrideConfig() { |
||
1683 | // Use PHP's built-in session handling, since MediaWiki's |
||
1684 | // SessionHandler can't work before we have an object cache set up. |
||
1685 | define( 'MW_NO_SESSION_HANDLER', 1 ); |
||
1686 | |||
1687 | // Don't access the database |
||
1688 | $GLOBALS['wgUseDatabaseMessages'] = false; |
||
1689 | // Don't cache langconv tables |
||
1690 | $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE; |
||
1691 | // Debug-friendly |
||
1692 | $GLOBALS['wgShowExceptionDetails'] = true; |
||
1693 | // Don't break forms |
||
1694 | $GLOBALS['wgExternalLinkTarget'] = '_blank'; |
||
1695 | |||
1696 | // Extended debugging |
||
1697 | $GLOBALS['wgShowSQLErrors'] = true; |
||
1698 | $GLOBALS['wgShowDBErrorBacktrace'] = true; |
||
1699 | |||
1700 | // Allow multiple ob_flush() calls |
||
1701 | $GLOBALS['wgDisableOutputCompression'] = true; |
||
1702 | |||
1703 | // Use a sensible cookie prefix (not my_wiki) |
||
1704 | $GLOBALS['wgCookiePrefix'] = 'mw_installer'; |
||
1705 | |||
1706 | // Some of the environment checks make shell requests, remove limits |
||
1707 | $GLOBALS['wgMaxShellMemory'] = 0; |
||
1708 | |||
1709 | // Override the default CookieSessionProvider with a dummy |
||
1710 | // implementation that won't stomp on PHP's cookies. |
||
1711 | $GLOBALS['wgSessionProviders'] = [ |
||
1712 | [ |
||
1713 | 'class' => 'InstallerSessionProvider', |
||
1714 | 'args' => [ [ |
||
1715 | 'priority' => 1, |
||
1716 | ] ] |
||
1717 | ] |
||
1718 | ]; |
||
1719 | |||
1720 | // Don't try to use any object cache for SessionManager either. |
||
1721 | $GLOBALS['wgSessionCacheType'] = CACHE_NONE; |
||
1722 | } |
||
1723 | |||
1724 | /** |
||
1725 | * Add an installation step following the given step. |
||
1726 | * |
||
1727 | * @param callable $callback A valid installation callback array, in this form: |
||
1728 | * [ 'name' => 'some-unique-name', 'callback' => [ $obj, 'function' ] ]; |
||
1729 | * @param string $findStep The step to find. Omit to put the step at the beginning |
||
1730 | */ |
||
1731 | public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { |
||
1732 | $this->extraInstallSteps[$findStep][] = $callback; |
||
1733 | } |
||
1734 | |||
1735 | /** |
||
1736 | * Disable the time limit for execution. |
||
1737 | * Some long-running pages (Install, Upgrade) will want to do this |
||
1738 | */ |
||
1739 | protected function disableTimeLimit() { |
||
1740 | MediaWiki\suppressWarnings(); |
||
1741 | set_time_limit( 0 ); |
||
1742 | MediaWiki\restoreWarnings(); |
||
1743 | } |
||
1744 | } |
||
1745 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: