* installers skips recreation of existing user
[lhc/web/wiklou.git] / includes / installer / Installer.php
1 <?php
2 /**
3 * Base code for MediaWiki installer.
4 *
5 * @file
6 * @ingroup Deployment
7 */
8
9 /**
10 * This documentation group collects source code files with deployment functionality.
11 *
12 * @defgroup Deployment Deployment
13 */
14
15 /**
16 * Base installer class.
17 *
18 * This class provides the base for installation and update functionality
19 * for both MediaWiki core and extensions.
20 *
21 * @ingroup Deployment
22 * @since 1.17
23 */
24 abstract class Installer {
25
26 /**
27 * TODO: make protected?
28 *
29 * @var array
30 */
31 public $settings;
32
33 /**
34 * Cached DB installer instances, access using getDBInstaller().
35 *
36 * @var array
37 */
38 protected $dbInstallers = array();
39
40 /**
41 * Minimum memory size in MB.
42 *
43 * @var integer
44 */
45 protected $minMemorySize = 50;
46
47 /**
48 * Cached Title, used by parse().
49 *
50 * @var Title
51 */
52 protected $parserTitle;
53
54 /**
55 * Cached ParserOptions, used by parse().
56 *
57 * @var ParserOptions
58 */
59 protected $parserOptions;
60
61 /**
62 * Known database types. These correspond to the class names <type>Installer,
63 * and are also MediaWiki database types valid for $wgDBtype.
64 *
65 * To add a new type, create a <type>Installer class and a Database<type>
66 * class, and add a config-type-<type> message to MessagesEn.php.
67 *
68 * @var array
69 */
70 protected static $dbTypes = array(
71 'mysql',
72 'postgres',
73 'oracle',
74 'sqlite',
75 );
76
77 /**
78 * A list of environment check methods called by doEnvironmentChecks().
79 * These may output warnings using showMessage(), and/or abort the
80 * installation process by returning false.
81 *
82 * @var array
83 */
84 protected $envChecks = array(
85 'envLatestVersion',
86 'envCheckDB',
87 'envCheckRegisterGlobals',
88 'envCheckMagicQuotes',
89 'envCheckMagicSybase',
90 'envCheckMbstring',
91 'envCheckZE1',
92 'envCheckSafeMode',
93 'envCheckXML',
94 'envCheckPCRE',
95 'envCheckMemory',
96 'envCheckCache',
97 'envCheckDiff3',
98 'envCheckGraphics',
99 'envCheckPath',
100 'envCheckExtension',
101 'envCheckShellLocale',
102 'envCheckUploadsDirectory',
103 'envCheckLibicu'
104 );
105
106 /**
107 * UI interface for displaying a short message
108 * The parameters are like parameters to wfMsg().
109 * The messages will be in wikitext format, which will be converted to an
110 * output format such as HTML or text before being sent to the user.
111 */
112 public abstract function showMessage( $msg /*, ... */ );
113
114 /**
115 * Constructor, always call this from child classes.
116 */
117 public function __construct() {
118 // Disable the i18n cache and LoadBalancer
119 Language::getLocalisationCache()->disableBackend();
120 LBFactory::disableBackend();
121 }
122
123 /**
124 * Get a list of known DB types.
125 */
126 public static function getDBTypes() {
127 return self::$dbTypes;
128 }
129
130 /**
131 * Do initial checks of the PHP environment. Set variables according to
132 * the observed environment.
133 *
134 * It's possible that this may be called under the CLI SAPI, not the SAPI
135 * that the wiki will primarily run under. In that case, the subclass should
136 * initialise variables such as wgScriptPath, before calling this function.
137 *
138 * Under the web subclass, it can already be assumed that PHP 5+ is in use
139 * and that sessions are working.
140 *
141 * @return boolean
142 */
143 public function doEnvironmentChecks() {
144 $this->showMessage( 'config-env-php', phpversion() );
145
146 $good = true;
147
148 foreach ( $this->envChecks as $check ) {
149 $status = $this->$check();
150 if ( $status === false ) {
151 $good = false;
152 }
153 }
154
155 $this->setVar( '_Environment', $good );
156
157 if ( $good ) {
158 $this->showMessage( 'config-env-good' );
159 } else {
160 $this->showMessage( 'config-env-bad' );
161 }
162
163 return $good;
164 }
165
166 /**
167 * Set a MW configuration variable, or internal installer configuration variable.
168 *
169 * @param $name String
170 * @param $value Mixed
171 */
172 public function setVar( $name, $value ) {
173 $this->settings[$name] = $value;
174 }
175
176 /**
177 * Get an MW configuration variable, or internal installer configuration variable.
178 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
179 * Installer variables are typically prefixed by an underscore.
180 *
181 * @param $name String
182 * @param $default Mixed
183 *
184 * @return mixed
185 */
186 public function getVar( $name, $default = null ) {
187 if ( !isset( $this->settings[$name] ) ) {
188 return $default;
189 } else {
190 return $this->settings[$name];
191 }
192 }
193
194 /**
195 * Get an instance of DatabaseInstaller for the specified DB type.
196 *
197 * @param $type Mixed: DB installer for which is needed, false to use default.
198 *
199 * @return DatabaseInstaller
200 */
201 public function getDBInstaller( $type = false ) {
202 if ( !$type ) {
203 $type = $this->getVar( 'wgDBtype' );
204 }
205
206 $type = strtolower( $type );
207
208 if ( !isset( $this->dbInstallers[$type] ) ) {
209 $class = ucfirst( $type ). 'Installer';
210 $this->dbInstallers[$type] = new $class( $this );
211 }
212
213 return $this->dbInstallers[$type];
214 }
215
216 /**
217 * Determine if LocalSettings exists. If it does, return an appropriate
218 * status for whether we should can upgrade or not.
219 *
220 * @return Status
221 */
222 public function getLocalSettingsStatus() {
223 global $IP;
224
225 $status = Status::newGood();
226
227 wfSuppressWarnings();
228 $ls = file_exists( "$IP/LocalSettings.php" );
229 wfRestoreWarnings();
230
231 if( $ls ) {
232 $wgCacheEpoch = $wgCommandLineMode = false;
233 require_once( "$IP/LocalSettings.php" );
234 $vars = get_defined_vars();
235 if( isset( $vars['wgUpgradeKey'] ) && $vars['wgUpgradeKey'] ) {
236 $status->warning( 'config-localsettings-upgrade' );
237 $this->setVar( '_UpgradeKey', $vars['wgUpgradeKey' ] );
238 } else {
239 $status->fatal( 'config-localsettings-noupgrade' );
240 }
241 }
242
243 return $status;
244 }
245
246 /**
247 * Get a fake password for sending back to the user in HTML.
248 * This is a security mechanism to avoid compromise of the password in the
249 * event of session ID compromise.
250 *
251 * @param $realPassword String
252 *
253 * @return string
254 */
255 public function getFakePassword( $realPassword ) {
256 return str_repeat( '*', strlen( $realPassword ) );
257 }
258
259 /**
260 * Set a variable which stores a password, except if the new value is a
261 * fake password in which case leave it as it is.
262 *
263 * @param $name String
264 * @param $value Mixed
265 */
266 public function setPassword( $name, $value ) {
267 if ( !preg_match( '/^\*+$/', $value ) ) {
268 $this->setVar( $name, $value );
269 }
270 }
271
272 /**
273 * On POSIX systems return the primary group of the webserver we're running under.
274 * On other systems just returns null.
275 *
276 * This is used to advice the user that he should chgrp his config/data/images directory as the
277 * webserver user before he can install.
278 *
279 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
280 *
281 * @return mixed
282 */
283 public static function maybeGetWebserverPrimaryGroup() {
284 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
285 # I don't know this, this isn't UNIX.
286 return null;
287 }
288
289 # posix_getegid() *not* getmygid() because we want the group of the webserver,
290 # not whoever owns the current script.
291 $gid = posix_getegid();
292 $getpwuid = posix_getpwuid( $gid );
293 $group = $getpwuid['name'];
294
295 return $group;
296 }
297
298 /**
299 * Convert wikitext $text to HTML.
300 *
301 * This is potentially error prone since many parser features require a complete
302 * installed MW database. The solution is to just not use those features when you
303 * write your messages. This appears to work well enough. Basic formatting and
304 * external links work just fine.
305 *
306 * But in case a translator decides to throw in a #ifexist or internal link or
307 * whatever, this function is guarded to catch attempted DB access and to present
308 * some fallback text.
309 *
310 * @param $text String
311 * @param $lineStart Boolean
312 * @return String
313 */
314 public function parse( $text, $lineStart = false ) {
315 global $wgParser;
316
317 try {
318 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
319 $html = $out->getText();
320 } catch ( DBAccessError $e ) {
321 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
322
323 if ( !empty( $this->debug ) ) {
324 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
325 }
326 }
327
328 return $html;
329 }
330
331 /**
332 * TODO: document
333 *
334 * @param $installer DatabaseInstaller
335 *
336 * @return Status
337 */
338 public function installDatabase( DatabaseInstaller &$installer ) {
339 if( !$installer ) {
340 $type = $this->getVar( 'wgDBtype' );
341 $status = Status::newFatal( "config-no-db", $type );
342 } else {
343 $status = $installer->setupDatabase();
344 }
345
346 return $status;
347 }
348
349 /**
350 * TODO: document
351 *
352 * @param $installer DatabaseInstaller
353 *
354 * @return Status
355 */
356 public function installTables( DatabaseInstaller &$installer ) {
357 $status = $installer->createTables();
358
359 if( $status->isOK() ) {
360 LBFactory::enableBackend();
361 }
362
363 return $status;
364 }
365
366 /**
367 * TODO: document
368 *
369 * @param $installer DatabaseInstaller
370 *
371 * @return Status
372 */
373 public function installInterwiki( DatabaseInstaller &$installer ) {
374 return $installer->populateInterwikiTable();
375 }
376
377 /**
378 * Exports all wg* variables stored by the installer into global scope.
379 */
380 public function exportVars() {
381 foreach ( $this->settings as $name => $value ) {
382 if ( substr( $name, 0, 2 ) == 'wg' ) {
383 $GLOBALS[$name] = $value;
384 }
385 }
386 }
387
388 /**
389 * Check if we're installing the latest version.
390 */
391 public function envLatestVersion() {
392 global $wgVersion;
393
394 $repository = wfGetRepository();
395 $currentVersion = $repository->getLatestCoreVersion();
396
397 $this->setVar( '_ExternalHTTP', true );
398
399 if ( $currentVersion === false ) {
400 # For when the request is successful but there's e.g. some silly man in
401 # the middle firewall blocking us, e.g. one of those annoying airport ones
402 $this->showMessage( 'config-env-latest-can-not-check', $repository->getLocation() );
403 return;
404 }
405
406 if( version_compare( $wgVersion, $currentVersion, '<' ) ) {
407 $this->showMessage( 'config-env-latest-old' );
408 // FIXME: this only works for the web installer!
409 $this->showHelpBox( 'config-env-latest-help', $wgVersion, $currentVersion );
410 } elseif( version_compare( $wgVersion, $currentVersion, '>' ) ) {
411 $this->showMessage( 'config-env-latest-new' );
412 } else {
413 $this->showMessage( 'config-env-latest-ok' );
414 }
415 }
416
417 /**
418 * Environment check for DB types.
419 */
420 public function envCheckDB() {
421 global $wgLang;
422
423 $compiledDBs = array();
424 $goodNames = array();
425 $allNames = array();
426
427 foreach ( self::getDBTypes() as $name ) {
428 $db = $this->getDBInstaller( $name );
429 $readableName = wfMsg( 'config-type-' . $name );
430
431 if ( $db->isCompiled() ) {
432 $compiledDBs[] = $name;
433 $goodNames[] = $readableName;
434 }
435
436 $allNames[] = $readableName;
437 }
438
439 $this->setVar( '_CompiledDBs', $compiledDBs );
440
441 if ( !$compiledDBs ) {
442 $this->showMessage( 'config-no-db' );
443 // FIXME: this only works for the web installer!
444 $this->showHelpBox( 'config-no-db-help', $wgLang->commaList( $allNames ) );
445 return false;
446 }
447
448 $this->showMessage( 'config-have-db', $wgLang->listToText( $goodNames ), count( $goodNames ) );
449
450 // Check for FTS3 full-text search module
451 $sqlite = $this->getDBInstaller( 'sqlite' );
452 if ( $sqlite->isCompiled() ) {
453 $db = new DatabaseSqliteStandalone( ':memory:' );
454 $this->showMessage( $db->getFulltextSearchModule() == 'FTS3'
455 ? 'config-have-fts3'
456 : 'config-no-fts3'
457 );
458 }
459 }
460
461 /**
462 * Environment check for register_globals.
463 */
464 public function envCheckRegisterGlobals() {
465 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
466 $this->showMessage( 'config-register-globals' );
467 }
468 }
469
470 /**
471 * Environment check for magic_quotes_runtime.
472 */
473 public function envCheckMagicQuotes() {
474 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
475 $this->showMessage( 'config-magic-quotes-runtime' );
476 return false;
477 }
478 }
479
480 /**
481 * Environment check for magic_quotes_sybase.
482 */
483 public function envCheckMagicSybase() {
484 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
485 $this->showMessage( 'config-magic-quotes-sybase' );
486 return false;
487 }
488 }
489
490 /**
491 * Environment check for mbstring.func_overload.
492 */
493 public function envCheckMbstring() {
494 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
495 $this->showMessage( 'config-mbstring' );
496 return false;
497 }
498 }
499
500 /**
501 * Environment check for zend.ze1_compatibility_mode.
502 */
503 public function envCheckZE1() {
504 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
505 $this->showMessage( 'config-ze1' );
506 return false;
507 }
508 }
509
510 /**
511 * Environment check for safe_mode.
512 */
513 public function envCheckSafeMode() {
514 if ( wfIniGetBool( 'safe_mode' ) ) {
515 $this->setVar( '_SafeMode', true );
516 $this->showMessage( 'config-safe-mode' );
517 }
518 }
519
520 /**
521 * Environment check for the XML module.
522 */
523 public function envCheckXML() {
524 if ( !function_exists( "utf8_encode" ) ) {
525 $this->showMessage( 'config-xml-bad' );
526 return false;
527 }
528 $this->showMessage( 'config-xml-good' );
529 }
530
531 /**
532 * Environment check for the PCRE module.
533 */
534 public function envCheckPCRE() {
535 if ( !function_exists( 'preg_match' ) ) {
536 $this->showMessage( 'config-pcre' );
537 return false;
538 }
539 }
540
541 /**
542 * Environment check for available memory.
543 */
544 public function envCheckMemory() {
545 $limit = ini_get( 'memory_limit' );
546
547 if ( !$limit || $limit == -1 ) {
548 $this->showMessage( 'config-memory-none' );
549 return true;
550 }
551
552 $n = intval( $limit );
553
554 if( preg_match( '/^([0-9]+)[Mm]$/', trim( $limit ), $m ) ) {
555 $n = intval( $m[1] * ( 1024 * 1024 ) );
556 }
557
558 if( $n < $this->minMemorySize * 1024 * 1024 ) {
559 $newLimit = "{$this->minMemorySize}M";
560
561 if( ini_set( "memory_limit", $newLimit ) === false ) {
562 $this->showMessage( 'config-memory-bad', $limit );
563 } else {
564 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
565 $this->setVar( '_RaiseMemory', true );
566 }
567 } else {
568 $this->showMessage( 'config-memory-ok', $limit );
569 }
570 }
571
572 /**
573 * Environment check for compiled object cache types.
574 */
575 public function envCheckCache() {
576 $caches = array();
577
578 foreach ( $this->objectCaches as $name => $function ) {
579 if ( function_exists( $function ) ) {
580 $caches[$name] = true;
581 $this->showMessage( 'config-' . $name );
582 }
583 }
584
585 if ( !$caches ) {
586 $this->showMessage( 'config-no-cache' );
587 }
588
589 $this->setVar( '_Caches', $caches );
590 }
591
592 /**
593 * Search for GNU diff3.
594 */
595 public function envCheckDiff3() {
596 $names = array( "gdiff3", "diff3", "diff3.exe" );
597 $versionInfo = array( '$1 --version 2>&1', 'diff3 (GNU diffutils)' );
598
599 $diff3 = $this->locateExecutableInDefaultPaths( $names, $versionInfo );
600
601 if ( $diff3 ) {
602 $this->showMessage( 'config-diff3-good', $diff3 );
603 $this->setVar( 'wgDiff3', $diff3 );
604 } else {
605 $this->setVar( 'wgDiff3', false );
606 $this->showMessage( 'config-diff3-bad' );
607 }
608 }
609
610 /**
611 * Environment check for ImageMagick and GD.
612 */
613 public function envCheckGraphics() {
614 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
615 $convert = $this->locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
616
617 if ( $convert ) {
618 $this->setVar( 'wgImageMagickConvertCommand', $convert );
619 $this->showMessage( 'config-imagemagick', $convert );
620 return true;
621 } elseif ( function_exists( 'imagejpeg' ) ) {
622 $this->showMessage( 'config-gd' );
623 return true;
624 } else {
625 $this->showMessage( 'no-scaling' );
626 }
627 }
628
629 /**
630 * Environment check for setting $IP and $wgScriptPath.
631 */
632 public function envCheckPath() {
633 global $IP;
634 $IP = dirname( dirname( dirname( __FILE__ ) ) );
635
636 $this->setVar( 'IP', $IP );
637 $this->showMessage( 'config-dir', $IP );
638
639 // PHP_SELF isn't available sometimes, such as when PHP is CGI but
640 // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
641 // to get the path to the current script... hopefully it's reliable. SIGH
642 if ( !empty( $_SERVER['PHP_SELF'] ) ) {
643 $path = $_SERVER['PHP_SELF'];
644 } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
645 $path = $_SERVER['SCRIPT_NAME'];
646 } elseif ( $this->getVar( 'wgScriptPath' ) ) {
647 // Some kind soul has set it for us already (e.g. debconf)
648 return true;
649 } else {
650 $this->showMessage( 'config-no-uri' );
651 return false;
652 }
653
654 $uri = preg_replace( '{^(.*)/config.*$}', '$1', $path );
655 $this->setVar( 'wgScriptPath', $uri );
656 $this->showMessage( 'config-uri', $uri );
657 }
658
659 /**
660 * Environment check for setting the preferred PHP file extension.
661 */
662 public function envCheckExtension() {
663 // FIXME: detect this properly
664 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
665 $ext = 'php5';
666 } else {
667 $ext = 'php';
668 }
669
670 $this->setVar( 'wgScriptExtension', ".$ext" );
671 $this->showMessage( 'config-file-extension', $ext );
672 }
673
674 /**
675 * TODO: document
676 */
677 public function envCheckShellLocale() {
678 $os = php_uname( 's' );
679 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
680
681 if ( !in_array( $os, $supported ) ) {
682 return true;
683 }
684
685 # Get a list of available locales.
686 $lines = $ret = false;
687 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
688
689 if ( $ret ) {
690 return true;
691 }
692
693 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
694 $candidatesByLocale = array();
695 $candidatesByLang = array();
696
697 foreach ( $lines as $line ) {
698 if ( $line === '' ) {
699 continue;
700 }
701
702 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
703 continue;
704 }
705
706 list( $all, $lang, $territory, $charset, $modifier ) = $m;
707
708 $candidatesByLocale[$m[0]] = $m;
709 $candidatesByLang[$lang][] = $m;
710 }
711
712 # Try the current value of LANG.
713 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
714 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
715 $this->showMessage( 'config-shell-locale', getenv( 'LANG' ) );
716 return true;
717 }
718
719 # Try the most common ones.
720 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
721 foreach ( $commonLocales as $commonLocale ) {
722 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
723 $this->setVar( 'wgShellLocale', $commonLocale );
724 $this->showMessage( 'config-shell-locale', $commonLocale );
725 return true;
726 }
727 }
728
729 # Is there an available locale in the Wiki's language?
730 $wikiLang = $this->getVar( 'wgLanguageCode' );
731
732 if ( isset( $candidatesByLang[$wikiLang] ) ) {
733 $m = reset( $candidatesByLang[$wikiLang] );
734 $this->setVar( 'wgShellLocale', $m[0] );
735 $this->showMessage( 'config-shell-locale', $m[0] );
736 return true;
737 }
738
739 # Are there any at all?
740 if ( count( $candidatesByLocale ) ) {
741 $m = reset( $candidatesByLocale );
742 $this->setVar( 'wgShellLocale', $m[0] );
743 $this->showMessage( 'config-shell-locale', $m[0] );
744 return true;
745 }
746
747 # Give up.
748 return true;
749 }
750
751 /**
752 * TODO: document
753 */
754 public function envCheckUploadsDirectory() {
755 global $IP, $wgServer;
756
757 $dir = $IP . '/images/';
758 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
759 $safe = !$this->dirIsExecutable( $dir, $url );
760
761 if ( $safe ) {
762 $this->showMessage( 'config-uploads-safe' );
763 } else {
764 $this->showMessage( 'config-uploads-not-safe', $dir );
765 }
766 }
767
768 /**
769 * Convert a hex string representing a Unicode code point to that code point.
770 * @param $c String
771 * @return string
772 */
773 protected function unicodeChar( $c ) {
774 $c = hexdec($c);
775 if ($c <= 0x7F) {
776 return chr($c);
777 } else if ($c <= 0x7FF) {
778 return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
779 } else if ($c <= 0xFFFF) {
780 return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
781 . chr(0x80 | $c & 0x3F);
782 } else if ($c <= 0x10FFFF) {
783 return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
784 . chr(0x80 | $c >> 6 & 0x3F)
785 . chr(0x80 | $c & 0x3F);
786 } else {
787 return false;
788 }
789 }
790
791
792 /**
793 * Check the libicu version
794 */
795 public function envCheckLibicu() {
796 $utf8 = function_exists( 'utf8_normalize' );
797 $intl = function_exists( 'normalizer_normalize' );
798
799 /**
800 * This needs to be updated something that the latest libicu
801 * will properly normalize. This normalization was found at
802 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
803 * Note that we use the hex representation to create the code
804 * points in order to avoid any Unicode-destroying during transit.
805 */
806 $not_normal_c = $this->unicodeChar("FA6C");
807 $normal_c = $this->unicodeChar("242EE");
808
809 $useNormalizer = 'php';
810 $needsUpdate = false;
811
812 /**
813 * We're going to prefer the pecl extension here unless
814 * utf8_normalize is more up to date.
815 */
816 if( $utf8 ) {
817 $useNormalizer = 'utf8';
818 $utf8 = utf8_normalize( $not_normal_c, UNORM_NFC );
819 if ( $utf8 !== $normal_c ) $needsUpdate = true;
820 }
821 if( $intl ) {
822 $useNormalizer = 'intl';
823 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
824 if ( $intl !== $normal_c ) $needsUpdate = true;
825 }
826
827 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
828 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
829 if( $useNormalizer === 'php' ) {
830 $this->showMessage( 'config-unicode-pure-php-warning' );
831 } elseif( $needsUpdate ) {
832 $this->showMessage( 'config-unicode-update-warning' );
833 }
834 }
835
836 /**
837 * Get an array of likely places we can find executables. Check a bunch
838 * of known Unix-like defaults, as well as the PATH environment variable
839 * (which should maybe make it work for Windows?)
840 *
841 * @return Array
842 */
843 protected function getPossibleBinPaths() {
844 return array_merge(
845 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
846 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
847 explode( PATH_SEPARATOR, getenv( 'PATH' ) )
848 );
849 }
850
851 /**
852 * Search a path for any of the given executable names. Returns the
853 * executable name if found. Also checks the version string returned
854 * by each executable.
855 *
856 * Used only by environment checks.
857 *
858 * @param $path String: path to search
859 * @param $names Array of executable names
860 * @param $versionInfo Boolean false or array with two members:
861 * 0 => Command to run for version check, with $1 for the path
862 * 1 => String to compare the output with
863 *
864 * If $versionInfo is not false, only executables with a version
865 * matching $versionInfo[1] will be returned.
866 */
867 protected function locateExecutable( $path, $names, $versionInfo = false ) {
868 if ( !is_array( $names ) ) {
869 $names = array( $names );
870 }
871
872 foreach ( $names as $name ) {
873 $command = $path . DIRECTORY_SEPARATOR . $name;
874
875 wfSuppressWarnings();
876 $file_exists = file_exists( $command );
877 wfRestoreWarnings();
878
879 if ( $file_exists ) {
880 if ( !$versionInfo ) {
881 return $command;
882 }
883
884 if ( wfIsWindows() ) {
885 $command = "\"$command\"";
886 }
887 $file = str_replace( '$1', $command, $versionInfo[0] );
888 if ( strstr( wfShellExec( $file ), $versionInfo[1]) !== false ) {
889 return $command;
890 }
891 }
892 }
893 return false;
894 }
895
896 /**
897 * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
898 * @see locateExecutable()
899 */
900 protected function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
901 foreach( $this->getPossibleBinPaths() as $path ) {
902 $exe = $this->locateExecutable( $path, $names, $versionInfo );
903 if( $exe !== false ) {
904 return $exe;
905 }
906 }
907 return false;
908 }
909
910 /**
911 * Checks if scripts located in the given directory can be executed via the given URL.
912 *
913 * Used only by environment checks.
914 */
915 public function dirIsExecutable( $dir, $url ) {
916 $scriptTypes = array(
917 'php' => array(
918 "<?php echo 'ex' . 'ec';",
919 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
920 ),
921 );
922
923 // it would be good to check other popular languages here, but it'll be slow.
924
925 wfSuppressWarnings();
926
927 foreach ( $scriptTypes as $ext => $contents ) {
928 foreach ( $contents as $source ) {
929 $file = 'exectest.' . $ext;
930
931 if ( !file_put_contents( $dir . $file, $source ) ) {
932 break;
933 }
934
935 $text = Http::get( $url . $file );
936 unlink( $dir . $file );
937
938 if ( $text == 'exec' ) {
939 wfRestoreWarnings();
940 return $ext;
941 }
942 }
943 }
944
945 wfRestoreWarnings();
946
947 return false;
948 }
949
950 }