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