]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/installer/WebInstallerPage.php
MediaWiki 1.17.0
[autoinstalls/mediawiki.git] / includes / installer / WebInstallerPage.php
1 <?php
2 /**
3  * Base code for web installer pages.
4  *
5  * @file
6  * @ingroup Deployment
7  */
8
9 /**
10  * Abstract class to define pages for the web installer.
11  *
12  * @ingroup Deployment
13  * @since 1.17
14  */
15 abstract class WebInstallerPage {
16
17         /**
18          * The WebInstaller object this WebInstallerPage belongs to.
19          *
20          * @var WebInstaller
21          */
22         public $parent;
23
24         public abstract function execute();
25
26         /**
27          * Constructor.
28          *
29          * @param $parent WebInstaller
30          */
31         public function __construct( WebInstaller $parent ) {
32                 $this->parent = $parent;
33         }
34
35         public function addHTML( $html ) {
36                 $this->parent->output->addHTML( $html );
37         }
38
39         public function startForm() {
40                 $this->addHTML(
41                         "<div class=\"config-section\">\n" .
42                         Html::openElement(
43                                 'form',
44                                 array(
45                                         'method' => 'post',
46                                         'action' => $this->parent->getUrl( array( 'page' => $this->getName() ) )
47                                 )
48                         ) . "\n"
49                 );
50         }
51
52         public function endForm( $continue = 'continue', $back = 'back' ) {
53                 $s = "<div class=\"config-submit\">\n";
54                 $id = $this->getId();
55
56                 if ( $id === false ) {
57                         $s .= Html::hidden( 'lastPage', $this->parent->request->getVal( 'lastPage' ) );
58                 }
59
60                 if ( $continue ) {
61                         // Fake submit button for enter keypress (bug 26267)
62                         $s .= Xml::submitButton( wfMsg( "config-$continue" ),
63                                 array( 'name' => "enter-$continue", 'style' => 'visibility:hidden;overflow:hidden;width:1px;margin:0' ) ) . "\n";
64                 }
65
66                 if ( $back ) {
67                         $s .= Xml::submitButton( wfMsg( "config-$back" ),
68                                 array(
69                                         'name' => "submit-$back",
70                                         'tabindex' => $this->parent->nextTabIndex()
71                                 ) ) . "\n";
72                 }
73
74                 if ( $continue ) {
75                         $s .= Xml::submitButton( wfMsg( "config-$continue" ),
76                                 array(
77                                         'name' => "submit-$continue",
78                                         'tabindex' => $this->parent->nextTabIndex(),
79                                 ) ) . "\n";
80                 }
81
82                 $s .= "</div></form></div>\n";
83                 $this->addHTML( $s );
84         }
85
86         public function getName() {
87                 return str_replace( 'WebInstaller_', '', get_class( $this ) );
88         }
89
90         protected function getId() {
91                 return array_search( $this->getName(), $this->parent->pageSequence );
92         }
93
94         public function getVar( $var ) {
95                 return $this->parent->getVar( $var );
96         }
97
98         public function setVar( $name, $value ) {
99                 $this->parent->setVar( $name, $value );
100         }
101
102         /**
103          * Get the starting tags of a fieldset.
104          *
105          * @param $legend String: message name
106          */
107         protected function getFieldsetStart( $legend ) {
108                 return "\n<fieldset><legend>" . wfMsgHtml( $legend ) . "</legend>\n";
109         }
110
111         /**
112          * Get the end tag of a fieldset.
113          */
114         protected function getFieldsetEnd() {
115                 return "</fieldset>\n";
116         }
117
118         /**
119          * Opens a textarea used to display the progress of a long operation
120          */
121         protected function startLiveBox() {
122                 $this->addHTML(
123                         '<div id="config-spinner" style="display:none;"><img src="../skins/common/images/ajax-loader.gif" /></div>' .
124                         '<script>jQuery( "#config-spinner" ).show();</script>' .
125                         '<textarea id="config-live-log" name="LiveLog" rows="10" cols="30" readonly="readonly">'
126                 );
127                 $this->parent->output->flush();
128         }
129
130         /**
131          * Opposite to startLiveBox()
132          */
133         protected function endLiveBox() {
134                 $this->addHTML( '</textarea>
135 <script>jQuery( "#config-spinner" ).hide()</script>' );
136                 $this->parent->output->flush();
137         }
138 }
139
140 class WebInstaller_Language extends WebInstallerPage {
141
142         public function execute() {
143                 global $wgLang;
144                 $r = $this->parent->request;
145                 $userLang = $r->getVal( 'UserLang' );
146                 $contLang = $r->getVal( 'ContLang' );
147
148                 $lifetime = intval( ini_get( 'session.gc_maxlifetime' ) );
149                 if ( !$lifetime ) {
150                         $lifetime = 1440; // PHP default
151                 }
152
153                 if ( $r->wasPosted() ) {
154                         # Do session test
155                         if ( $this->parent->getSession( 'test' ) === null ) {
156                                 $requestTime = $r->getVal( 'LanguageRequestTime' );
157                                 if ( !$requestTime ) {
158                                         // The most likely explanation is that the user was knocked back
159                                         // from another page on POST due to session expiry
160                                         $msg = 'config-session-expired';
161                                 } elseif ( time() - $requestTime > $lifetime ) {
162                                         $msg = 'config-session-expired';
163                                 } else {
164                                         $msg = 'config-no-session';
165                                 }
166                                 $this->parent->showError( $msg, $wgLang->formatTimePeriod( $lifetime ) );
167                         } else {
168                                 $languages = Language::getLanguageNames();
169                                 if ( isset( $languages[$userLang] ) ) {
170                                         $this->setVar( '_UserLang', $userLang );
171                                 }
172                                 if ( isset( $languages[$contLang] ) ) {
173                                         $this->setVar( 'wgLanguageCode', $contLang );
174                                 }
175                                 return 'continue';
176                         }
177                 } elseif ( $this->parent->showSessionWarning ) {
178                         # The user was knocked back from another page to the start
179                         # This probably indicates a session expiry
180                         $this->parent->showError( 'config-session-expired', $wgLang->formatTimePeriod( $lifetime ) );
181                 }
182
183                 $this->parent->setSession( 'test', true );
184
185                 if ( !isset( $languages[$userLang] ) ) {
186                         $userLang = $this->getVar( '_UserLang', 'en' );
187                 }
188                 if ( !isset( $languages[$contLang] ) ) {
189                         $contLang = $this->getVar( 'wgLanguageCode', 'en' );
190                 }
191                 $this->startForm();
192                 $s = Html::hidden( 'LanguageRequestTime', time() ) .
193                         $this->getLanguageSelector( 'UserLang', 'config-your-language', $userLang, $this->parent->getHelpBox( 'config-your-language-help' ) ) .
194                         $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang, $this->parent->getHelpBox( 'config-wiki-language-help' ) );
195                 $this->addHTML( $s );
196                 $this->endForm( 'continue', false );
197         }
198
199         /**
200          * Get a <select> for selecting languages.
201          */
202         public function getLanguageSelector( $name, $label, $selectedCode ) {
203                 global $wgDummyLanguageCodes;
204                 $s = Html::openElement( 'select', array( 'id' => $name, 'name' => $name ) ) . "\n";
205
206                 $languages = Language::getLanguageNames();
207                 ksort( $languages );
208                 $dummies = array_flip( $wgDummyLanguageCodes );
209                 foreach ( $languages as $code => $lang ) {
210                         if ( isset( $dummies[$code] ) ) continue;
211                         $s .= "\n" . Xml::option( "$code - $lang", $code, $code == $selectedCode );
212                 }
213                 $s .= "\n</select>\n";
214                 return $this->parent->label( $label, $name, $s );
215         }
216
217 }
218
219 class WebInstaller_ExistingWiki extends WebInstallerPage {
220         public function execute() {
221                 // If there is no LocalSettings.php, continue to the installer welcome page
222                 $vars = Installer::getExistingLocalSettings();
223                 if ( !$vars ) {
224                         return 'skip';
225                 }
226
227                 // Check if the upgrade key supplied to the user has appeared in LocalSettings.php
228                 if ( $vars['wgUpgradeKey'] !== false
229                         && $this->getVar( '_UpgradeKeySupplied' )
230                         && $this->getVar( 'wgUpgradeKey' ) === $vars['wgUpgradeKey'] )
231                 {
232                         // It's there, so the user is authorized
233                         $status = $this->handleExistingUpgrade( $vars );
234                         if ( $status->isOK() ) {
235                                 return 'skip';
236                         } else {
237                                 $this->startForm();
238                                 $this->parent->showStatusBox( $status );
239                                 $this->endForm( 'continue' );
240                                 return 'output';
241                         }
242                 }
243
244                 // If there is no $wgUpgradeKey, tell the user to add one to LocalSettings.php
245                 if ( $vars['wgUpgradeKey'] === false ) {
246                         if ( $this->getVar( 'wgUpgradeKey', false ) === false ) {
247                                 $secretKey = $this->getVar( 'wgSecretKey' ); // preserve $wgSecretKey
248                                 $this->parent->generateKeys();
249                                 $this->setVar( 'wgSecretKey', $secretKey );
250                                 $this->setVar( '_UpgradeKeySupplied', true );
251                         }
252                         $this->startForm();
253                         $this->addHTML( $this->parent->getInfoBox(
254                                 wfMsgNoTrans( 'config-upgrade-key-missing',
255                                         "<pre>\$wgUpgradeKey = '" . $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )
256                         ) );
257                         $this->endForm( 'continue' );
258                         return 'output';
259                 }
260
261                 // If there is an upgrade key, but it wasn't supplied, prompt the user to enter it
262
263                 $r = $this->parent->request;
264                 if ( $r->wasPosted() ) {
265                         $key = $r->getText( 'config_wgUpgradeKey' );
266                         if( !$key || $key !== $vars['wgUpgradeKey'] ) {
267                                 $this->parent->showError( 'config-localsettings-badkey' );
268                                 $this->showKeyForm();
269                                 return 'output';
270                         }
271                         // Key was OK
272                         $status = $this->handleExistingUpgrade( $vars );
273                         if ( $status->isOK() ) {
274                                 return 'continue';
275                         } else {
276                                 $this->parent->showStatusBox( $status );
277                                 $this->showKeyForm();
278                                 return 'output';
279                         }
280                 } else {
281                         $this->showKeyForm();
282                         return 'output';
283                 }
284         }
285
286         /**
287          * Show the "enter key" form
288          */
289         protected function showKeyForm() {
290                 $this->startForm();
291                 $this->addHTML(
292                         $this->parent->getInfoBox( wfMsgNoTrans( 'config-localsettings-upgrade' ) ).
293                         '<br />' .
294                         $this->parent->getTextBox( array(
295                                 'var' => 'wgUpgradeKey',
296                                 'label' => 'config-localsettings-key',
297                                 'attribs' => array( 'autocomplete' => 'off' ),
298                         ) )
299                 );
300                 $this->endForm( 'continue' );
301         }
302
303         protected function importVariables( $names, $vars ) {
304                 $status = Status::newGood();
305                 foreach ( $names as $name ) {
306                         if ( !isset( $vars[$name] ) ) {
307                                 $status->fatal( 'config-localsettings-incomplete', $name );
308                         }
309                         $this->setVar( $name, $vars[$name] );
310                 }
311                 return $status;
312         }
313
314         /**
315          * Initiate an upgrade of the existing database
316          * @param $vars Variables from LocalSettings.php and AdminSettings.php
317          * @return Status
318          */
319         protected function handleExistingUpgrade( $vars ) {
320                 // Check $wgDBtype
321                 if ( !isset( $vars['wgDBtype'] ) || !in_array( $vars['wgDBtype'], Installer::getDBTypes() ) ) {
322                         return Status::newFatal( 'config-localsettings-connection-error', '' );
323                 }
324
325                 // Set the relevant variables from LocalSettings.php
326                 $requiredVars = array( 'wgDBtype' );
327                 $status = $this->importVariables( $requiredVars , $vars );
328                 $installer = $this->parent->getDBInstaller();
329                 $status->merge( $this->importVariables( $installer->getGlobalNames(), $vars ) );
330                 if ( !$status->isOK() ) {
331                         return $status;
332                 }
333
334                 if ( isset( $vars['wgDBadminuser'] ) ) {
335                         $this->setVar( '_InstallUser', $vars['wgDBadminuser'] );
336                 } else {
337                         $this->setVar( '_InstallUser', $vars['wgDBuser'] );
338                 }
339                 if ( isset( $vars['wgDBadminpassword'] ) ) {
340                         $this->setVar( '_InstallPassword', $vars['wgDBadminpassword'] );
341                 } else {
342                         $this->setVar( '_InstallPassword', $vars['wgDBpassword'] );
343                 }
344
345                 // Test the database connection
346                 $status = $installer->getConnection();
347                 if ( !$status->isOK() ) {
348                         // Adjust the error message to explain things correctly
349                         $status->replaceMessage( 'config-connection-error',
350                                 'config-localsettings-connection-error' );
351                         return $status;
352                 }
353
354                 // All good
355                 $this->setVar( '_ExistingDBSettings', true );
356                 return $status;
357         }
358 }
359
360 class WebInstaller_Welcome extends WebInstallerPage {
361
362         public function execute() {
363                 if ( $this->parent->request->wasPosted() ) {
364                         if ( $this->getVar( '_Environment' ) ) {
365                                 return 'continue';
366                         }
367                 }
368                 $this->parent->output->addWikiText( wfMsgNoTrans( 'config-welcome' ) );
369                 $status = $this->parent->doEnvironmentChecks();
370                 if ( $status->isGood() ) {
371                         $this->parent->output->addHTML( '<span class="success-message">' .
372                                 wfMsgHtml( 'config-env-good' ) . '</span>' );
373                         $this->parent->output->addWikiText( wfMsgNoTrans( 'config-copyright',
374                                 SpecialVersion::getCopyrightAndAuthorList() ) );
375                         $this->startForm();
376                         $this->endForm();
377                 } else {
378                         $this->parent->showStatusMessage( $status );
379                 }
380         }
381
382 }
383
384 class WebInstaller_DBConnect extends WebInstallerPage {
385
386         public function execute() {
387                 if ( $this->getVar( '_ExistingDBSettings' ) ) {
388                         return 'skip';
389                 }
390
391                 $r = $this->parent->request;
392                 if ( $r->wasPosted() ) {
393                         $status = $this->submit();
394
395                         if ( $status->isGood() ) {
396                                 $this->setVar( '_UpgradeDone', false );
397                                 return 'continue';
398                         } else {
399                                 $this->parent->showStatusBox( $status );
400                         }
401                 }
402
403                 $this->startForm();
404
405                 $types = "<ul class=\"config-settings-block\">\n";
406                 $settings = '';
407                 $defaultType = $this->getVar( 'wgDBtype' );
408
409                 $dbSupport = '';
410                 foreach( $this->parent->getDBTypes() as $type ) {
411                         $link = DatabaseBase::newFromType( $type )->getSoftwareLink();
412                         $dbSupport .= wfMsgNoTrans( "config-support-$type", $link ) . "\n";
413                 }
414                 $this->addHTML( $this->parent->getInfoBox(
415                         wfMsg( 'config-support-info', $dbSupport ) ) );
416
417                 foreach ( $this->parent->getVar( '_CompiledDBs' ) as $type ) {
418                         $installer = $this->parent->getDBInstaller( $type );
419                         $types .=
420                                 '<li>' .
421                                 Xml::radioLabel(
422                                         $installer->getReadableName(),
423                                         'DBType',
424                                         $type,
425                                         "DBType_$type",
426                                         $type == $defaultType,
427                                         array( 'class' => 'dbRadio', 'rel' => "DB_wrapper_$type" )
428                                 ) .
429                                 "</li>\n";
430
431                         $settings .=
432                                 Html::openElement( 'div', array( 'id' => 'DB_wrapper_' . $type, 'class' => 'dbWrapper' ) ) .
433                                 Html::element( 'h3', array(), wfMsg( 'config-header-' . $type ) ) .
434                                 $installer->getConnectForm() .
435                                 "</div>\n";
436                 }
437                 $types .= "</ul><br clear=\"left\"/>\n";
438
439                 $this->addHTML(
440                         $this->parent->label( 'config-db-type', false, $types ) .
441                         $settings
442                 );
443
444                 $this->endForm();
445         }
446
447         public function submit() {
448                 $r = $this->parent->request;
449                 $type = $r->getVal( 'DBType' );
450                 $this->setVar( 'wgDBtype', $type );
451                 $installer = $this->parent->getDBInstaller( $type );
452                 if ( !$installer ) {
453                         return Status::newFatal( 'config-invalid-db-type' );
454                 }
455                 return $installer->submitConnectForm();
456         }
457
458 }
459
460 class WebInstaller_Upgrade extends WebInstallerPage {
461
462         public function execute() {
463                 if ( $this->getVar( '_UpgradeDone' ) ) {
464                         // Allow regeneration of LocalSettings.php, unless we are working
465                         // from a pre-existing LocalSettings.php file and we want to avoid
466                         // leaking its contents
467                         if ( $this->parent->request->wasPosted() && !$this->getVar( '_ExistingDBSettings' ) ) {
468                                 // Done message acknowledged
469                                 return 'continue';
470                         } else {
471                                 // Back button click
472                                 // Show the done message again
473                                 // Make them click back again if they want to do the upgrade again
474                                 $this->showDoneMessage();
475                                 return 'output';
476                         }
477                 }
478
479                 // wgDBtype is generally valid here because otherwise the previous page
480                 // (connect) wouldn't have declared its happiness
481                 $type = $this->getVar( 'wgDBtype' );
482                 $installer = $this->parent->getDBInstaller( $type );
483
484                 if ( !$installer->needsUpgrade() ) {
485                         return 'skip';
486                 }
487
488                 if ( $this->parent->request->wasPosted() ) {
489                         $installer->preUpgrade();
490
491                         $this->startLiveBox();
492                         $result = $installer->doUpgrade();
493                         $this->endLiveBox();
494
495                         if ( $result ) {
496                                 // If they're going to possibly regenerate LocalSettings, we
497                                 // need to create the upgrade/secret keys. Bug 26481
498                                 if( !$this->getVar( '_ExistingDBSettings' ) ) {
499                                         $this->parent->generateKeys();
500                                 }
501                                 $this->setVar( '_UpgradeDone', true );
502                                 $this->showDoneMessage();
503                                 return 'output';
504                         }
505                 }
506
507                 $this->startForm();
508                 $this->addHTML( $this->parent->getInfoBox(
509                         wfMsgNoTrans( 'config-can-upgrade', $GLOBALS['wgVersion'] ) ) );
510                 $this->endForm();
511         }
512
513         public function showDoneMessage() {
514                 $this->startForm();
515                 $regenerate = !$this->getVar( '_ExistingDBSettings' );
516                 if ( $regenerate ) {
517                         $msg = 'config-upgrade-done';
518                 } else {
519                         $msg = 'config-upgrade-done-no-regenerate';
520                 }
521                 $this->parent->disableLinkPopups();
522                 $this->addHTML(
523                         $this->parent->getInfoBox(
524                                 wfMsgNoTrans( $msg,
525                                         $GLOBALS['wgServer'] .
526                                                 $this->getVar( 'wgScriptPath' ) . '/index' .
527                                                 $this->getVar( 'wgScriptExtension' )
528                                 ), 'tick-32.png'
529                         )
530                 );
531                 $this->parent->restoreLinkPopups();
532                 $this->endForm( $regenerate ? 'regenerate' : false, false );
533         }
534
535 }
536
537 class WebInstaller_DBSettings extends WebInstallerPage {
538
539         public function execute() {
540                 $installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
541
542                 $r = $this->parent->request;
543                 if ( $r->wasPosted() ) {
544                         $status = $installer->submitSettingsForm();
545                         if ( $status === false ) {
546                                 return 'skip';
547                         } elseif ( $status->isGood() ) {
548                                 return 'continue';
549                         } else {
550                                 $this->parent->showStatusBox( $status );
551                         }
552                 }
553
554                 $form = $installer->getSettingsForm();
555                 if ( $form === false ) {
556                         return 'skip';
557                 }
558
559                 $this->startForm();
560                 $this->addHTML( $form );
561                 $this->endForm();
562         }
563
564 }
565
566 class WebInstaller_Name extends WebInstallerPage {
567
568         public function execute() {
569                 $r = $this->parent->request;
570                 if ( $r->wasPosted() ) {
571                         if ( $this->submit() ) {
572                                 return 'continue';
573                         }
574                 }
575
576                 $this->startForm();
577
578                 // Encourage people to not name their site 'MediaWiki' by blanking the
579                 // field. I think that was the intent with the original $GLOBALS['wgSitename']
580                 // but these two always were the same so had the effect of making the
581                 // installer forget $wgSitename when navigating back to this page.
582                 if ( $this->getVar( 'wgSitename' ) == 'MediaWiki' ) {
583                         $this->setVar( 'wgSitename', '' );
584                 }
585
586                 // Set wgMetaNamespace to something valid before we show the form.
587                 // $wgMetaNamespace defaults to $wgSiteName which is 'MediaWiki'
588                 $metaNS = $this->getVar( 'wgMetaNamespace' );
589                 $this->setVar( 'wgMetaNamespace', wfMsgForContent( 'config-ns-other-default' ) );
590
591                 $this->addHTML(
592                         $this->parent->getTextBox( array(
593                                 'var' => 'wgSitename',
594                                 'label' => 'config-site-name',
595                           'help' => $this->parent->getHelpBox( 'config-site-name-help' )
596                         ) ) .
597                         $this->parent->getRadioSet( array(
598                                 'var' => '_NamespaceType',
599                                 'label' => 'config-project-namespace',
600                                 'itemLabelPrefix' => 'config-ns-',
601                                 'values' => array( 'site-name', 'generic', 'other' ),
602                                 'commonAttribs' => array( 'class' => 'enableForOther', 'rel' => 'config_wgMetaNamespace' ),
603                                 'help' => $this->parent->getHelpBox( 'config-project-namespace-help' )
604                         ) ) .
605                         $this->parent->getTextBox( array(
606                                 'var' => 'wgMetaNamespace',
607                                 'label' => '', //TODO: Needs a label?
608                                 'attribs' => array( 'readonly' => 'readonly', 'class' => 'enabledByOther' ),
609
610                         ) ) .
611                         $this->getFieldSetStart( 'config-admin-box' ) .
612                         $this->parent->getTextBox( array(
613                                 'var' => '_AdminName',
614                                 'label' => 'config-admin-name',
615                                 'help' => $this->parent->getHelpBox( 'config-admin-help' )
616                         ) ) .
617                         $this->parent->getPasswordBox( array(
618                                 'var' => '_AdminPassword',
619                                 'label' => 'config-admin-password',
620                         ) ) .
621                         $this->parent->getPasswordBox( array(
622                                 'var' => '_AdminPassword2',
623                                 'label' => 'config-admin-password-confirm'
624                         ) ) .
625                         $this->parent->getTextBox( array(
626                                 'var' => '_AdminEmail',
627                                 'label' => 'config-admin-email',
628                                 'help' => $this->parent->getHelpBox( 'config-admin-email-help' )
629                         ) ) .
630                         $this->parent->getCheckBox( array(
631                                 'var' => '_Subscribe',
632                                 'label' => 'config-subscribe',
633                                 'help' => $this->parent->getHelpBox( 'config-subscribe-help' )
634                         ) ) .
635                         $this->getFieldSetEnd() .
636                         $this->parent->getInfoBox( wfMsg( 'config-almost-done' ) ) .
637                         $this->parent->getRadioSet( array(
638                                 'var' => '_SkipOptional',
639                                 'itemLabelPrefix' => 'config-optional-',
640                                 'values' => array( 'continue', 'skip' )
641                         ) )
642                 );
643
644                 // Restore the default value
645                 $this->setVar( 'wgMetaNamespace', $metaNS );
646
647                 $this->endForm();
648                 return 'output';
649         }
650
651         public function submit() {
652                 $retVal = true;
653                 $this->parent->setVarsFromRequest( array( 'wgSitename', '_NamespaceType',
654                         '_AdminName', '_AdminPassword', '_AdminPassword2', '_AdminEmail',
655                         '_Subscribe', '_SkipOptional', 'wgMetaNamespace' ) );
656
657                 // Validate site name
658                 if ( strval( $this->getVar( 'wgSitename' ) ) === '' ) {
659                         $this->parent->showError( 'config-site-name-blank' );
660                         $retVal = false;
661                 }
662
663                 // Fetch namespace
664                 $nsType = $this->getVar( '_NamespaceType' );
665                 if ( $nsType == 'site-name' ) {
666                         $name = $this->getVar( 'wgSitename' );
667                         // Sanitize for namespace
668                         // This algorithm should match the JS one in WebInstallerOutput.php
669                         $name = preg_replace( '/[\[\]\{\}|#<>%+? ]/', '_', $name );
670                         $name = str_replace( '&', '&amp;', $name );
671                         $name = preg_replace( '/__+/', '_', $name );
672                         $name = ucfirst( trim( $name, '_' ) );
673                 } elseif ( $nsType == 'generic' ) {
674                         $name = wfMsg( 'config-ns-generic' );
675                 } else { // other
676                         $name = $this->getVar( 'wgMetaNamespace' );
677                 }
678
679                 // Validate namespace
680                 if ( strpos( $name, ':' ) !== false ) {
681                         $good = false;
682                 } else {
683                         // Title-style validation
684                         $title = Title::newFromText( $name );
685                         if ( !$title ) {
686                                 $good = $nsType == 'site-name';
687                         } else {
688                                 $name = $title->getDBkey();
689                                 $good = true;
690                         }
691                 }
692                 if ( !$good ) {
693                         $this->parent->showError( 'config-ns-invalid', $name );
694                         $retVal = false;
695                 }
696
697                 // Make sure it won't conflict with any existing namespaces
698                 global $wgContLang;
699                 $nsIndex = $wgContLang->getNsIndex( $name );
700                 if( $nsIndex !== false && $nsIndex !== NS_PROJECT ) {
701                         $this->parent->showError( 'config-ns-conflict', $name );
702                         $retVal = false;
703                 }
704
705                 $this->setVar( 'wgMetaNamespace', $name );
706
707                 // Validate username for creation
708                 $name = $this->getVar( '_AdminName' );
709                 if ( strval( $name ) === '' ) {
710                         $this->parent->showError( 'config-admin-name-blank' );
711                         $cname = $name;
712                         $retVal = false;
713                 } else {
714                         $cname = User::getCanonicalName( $name, 'creatable' );
715                         if ( $cname === false ) {
716                                 $this->parent->showError( 'config-admin-name-invalid', $name );
717                                 $retVal = false;
718                         } else {
719                                 $this->setVar( '_AdminName', $cname );
720                         }
721                 }
722
723                 // Validate password
724                 $msg = false;
725                 $valid = false;
726                 $pwd = $this->getVar( '_AdminPassword' );
727                 $user = User::newFromName( $cname );
728                 $valid = $user && $user->getPasswordValidity( $pwd );
729                 if ( strval( $pwd ) === '' ) {
730                         # $user->getPasswordValidity just checks for $wgMinimalPasswordLength.
731                         # This message is more specific and helpful.
732                         $msg = 'config-admin-password-blank';
733                 } elseif ( $pwd !== $this->getVar( '_AdminPassword2' ) ) {
734                         $msg = 'config-admin-password-mismatch';
735                 } elseif ( $valid !== true ) {
736                         # As of writing this will only catch the username being e.g. 'FOO' and
737                         # the password 'foo'
738                         $msg = $valid;
739                 }
740                 if ( $msg !== false ) {
741                         call_user_func_array( array( $this->parent, 'showError' ), (array)$msg );
742                         $this->setVar( '_AdminPassword', '' );
743                         $this->setVar( '_AdminPassword2', '' );
744                         $retVal = false;
745                 }
746
747                 // Validate e-mail if provided
748                 $email = $this->getVar( '_AdminEmail' );
749                 if( $email && !User::isValidEmailAddr( $email ) ) {
750                         $this->parent->showError( 'config-admin-error-bademail' );
751                         $retVal = false;
752                 }
753
754                 return $retVal;
755         }
756
757 }
758
759 class WebInstaller_Options extends WebInstallerPage {
760
761         public function execute() {
762                 if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
763                         return 'skip';
764                 }
765                 if ( $this->parent->request->wasPosted() ) {
766                         if ( $this->submit() ) {
767                                 return 'continue';
768                         }
769                 }
770
771                 $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
772                 $this->startForm();
773                 $this->addHTML(
774                         # User Rights
775                         $this->parent->getRadioSet( array(
776                                 'var' => '_RightsProfile',
777                                 'label' => 'config-profile',
778                                 'itemLabelPrefix' => 'config-profile-',
779                                 'values' => array_keys( $this->parent->rightsProfiles ),
780                         ) ) .
781                         $this->parent->getInfoBox( wfMsgNoTrans( 'config-profile-help' ) ) .
782
783                         # Licensing
784                         $this->parent->getRadioSet( array(
785                                 'var' => '_LicenseCode',
786                                 'label' => 'config-license',
787                                 'itemLabelPrefix' => 'config-license-',
788                                 'values' => array_keys( $this->parent->licenses ),
789                                 'commonAttribs' => array( 'class' => 'licenseRadio' ),
790                         ) ) .
791                         $this->getCCChooser() .
792                         $this->parent->getHelpBox( 'config-license-help' ) .
793
794                         # E-mail
795                         $this->getFieldSetStart( 'config-email-settings' ) .
796                         $this->parent->getCheckBox( array(
797                                 'var' => 'wgEnableEmail',
798                                 'label' => 'config-enable-email',
799                                 'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ),
800                         ) ) .
801                         $this->parent->getHelpBox( 'config-enable-email-help' ) .
802                         "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
803                         $this->parent->getTextBox( array(
804                                 'var' => 'wgPasswordSender',
805                                 'label' => 'config-email-sender'
806                         ) ) .
807                         $this->parent->getHelpBox( 'config-email-sender-help' ) .
808                         $this->parent->getCheckBox( array(
809                                 'var' => 'wgEnableUserEmail',
810                                 'label' => 'config-email-user',
811                         ) ) .
812                         $this->parent->getHelpBox( 'config-email-user-help' ) .
813                         $this->parent->getCheckBox( array(
814                                 'var' => 'wgEnotifUserTalk',
815                                 'label' => 'config-email-usertalk',
816                         ) ) .
817                         $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
818                         $this->parent->getCheckBox( array(
819                                 'var' => 'wgEnotifWatchlist',
820                                 'label' => 'config-email-watchlist',
821                         ) ) .
822                         $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
823                         $this->parent->getCheckBox( array(
824                                 'var' => 'wgEmailAuthentication',
825                                 'label' => 'config-email-auth',
826                         ) ) .
827                         $this->parent->getHelpBox( 'config-email-auth-help' ) .
828                         "</div>" .
829                         $this->getFieldSetEnd()
830                 );
831
832                 $extensions = $this->parent->findExtensions();
833
834                 if( $extensions ) {
835                         $extHtml = $this->getFieldSetStart( 'config-extensions' );
836
837                         foreach( $extensions as $ext ) {
838                                 $extHtml .= $this->parent->getCheckBox( array(
839                                         'var' => "ext-$ext",
840                                         'rawtext' => $ext,
841                                 ) );
842                         }
843
844                         $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
845                         $this->getFieldSetEnd();
846                         $this->addHTML( $extHtml );
847                 }
848
849                 // Having / in paths in Windows looks funny :)
850                 $this->setVar( 'wgDeletedDirectory',
851                         str_replace(
852                                 '/', DIRECTORY_SEPARATOR,
853                                 $this->getVar( 'wgDeletedDirectory' )
854                         )
855                 );
856
857                 $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
858                 $this->addHTML(
859                         # Uploading
860                         $this->getFieldSetStart( 'config-upload-settings' ) .
861                         $this->parent->getCheckBox( array(
862                                 'var' => 'wgEnableUploads',
863                                 'label' => 'config-upload-enable',
864                                 'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ),
865                                 'help' => $this->parent->getHelpBox( 'config-upload-help' )
866                         ) ) .
867                         '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
868                         $this->parent->getTextBox( array(
869                                 'var' => 'wgDeletedDirectory',
870                                 'label' => 'config-upload-deleted',
871                                 'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
872                         ) ) .
873                         '</div>' .
874                         $this->parent->getTextBox( array(
875                                 'var' => 'wgLogo',
876                                 'label' => 'config-logo',
877                                 'help' => $this->parent->getHelpBox( 'config-logo-help' )
878                         ) )
879                 );
880                 $this->addHTML(
881                         $this->parent->getCheckBox( array(
882                                 'var' => 'wgUseInstantCommons',
883                                 'label' => 'config-instantcommons',
884                                 'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
885                         ) ) .
886                         $this->getFieldSetEnd()
887                 );
888
889                 $caches = array( 'none' );
890                 if( count( $this->getVar( '_Caches' ) ) ) {
891                         $caches[] = 'accel';
892                 }
893                 $caches[] = 'memcached';
894
895                 $this->addHTML(
896                         # Advanced settings
897                         $this->getFieldSetStart( 'config-advanced-settings' ) .
898                         # Object cache settings
899                         $this->parent->getRadioSet( array(
900                                 'var' => 'wgMainCacheType',
901                                 'label' => 'config-cache-options',
902                                 'itemLabelPrefix' => 'config-cache-',
903                                 'values' => $caches,
904                                 'value' => 'none',
905                         ) ) .
906                         $this->parent->getHelpBox( 'config-cache-help' ) .
907                         '<div id="config-memcachewrapper">' .
908                         $this->parent->getTextArea( array(
909                                 'var' => '_MemCachedServers',
910                                 'label' => 'config-memcached-servers',
911                                 'help' => $this->parent->getHelpBox( 'config-memcached-help' )
912                         ) ) .
913                         '</div>' .
914                         $this->getFieldSetEnd()
915                 );
916                 $this->endForm();
917         }
918
919         public function getCCPartnerUrl() {
920                 global $wgServer;
921                 $exitUrl = $wgServer . $this->parent->getUrl( array(
922                         'page' => 'Options',
923                         'SubmitCC' => 'indeed',
924                         'config__LicenseCode' => 'cc',
925                         'config_wgRightsUrl' => '[license_url]',
926                         'config_wgRightsText' => '[license_name]',
927                         'config_wgRightsIcon' => '[license_button]',
928                 ) );
929                 $styleUrl = $wgServer . dirname( dirname( $this->parent->getUrl() ) ) .
930                         '/skins/common/config-cc.css';
931                 $iframeUrl = 'http://creativecommons.org/license/?' .
932                         wfArrayToCGI( array(
933                                 'partner' => 'MediaWiki',
934                                 'exit_url' => $exitUrl,
935                                 'lang' => $this->getVar( '_UserLang' ),
936                                 'stylesheet' => $styleUrl,
937                         ) );
938                 return $iframeUrl;
939         }
940
941         public function getCCChooser() {
942                 $iframeAttribs = array(
943                         'class' => 'config-cc-iframe',
944                         'name' => 'config-cc-iframe',
945                         'id' => 'config-cc-iframe',
946                         'frameborder' => 0,
947                         'width' => '100%',
948                         'height' => '100%',
949                 );
950                 if ( $this->getVar( '_CCDone' ) ) {
951                         $iframeAttribs['src'] = $this->parent->getUrl( array( 'ShowCC' => 'yes' ) );
952                 } else {
953                         $iframeAttribs['src'] = $this->getCCPartnerUrl();
954                 }
955                 $wrapperStyle = ($this->getVar('_LicenseCode') == 'cc-choose') ? '' : 'display: none';
956
957                 return
958                         "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
959                         Html::element( 'iframe', $iframeAttribs, '', false /* not short */ ) .
960                         "</div>\n";
961         }
962
963         public function getCCDoneBox() {
964                 $js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
965                 // If you change this height, also change it in config.css
966                 $expandJs = str_replace( '$1', '54em', $js );
967                 $reduceJs = str_replace( '$1', '70px', $js );
968                 return
969                         '<p>'.
970                         Html::element( 'img', array( 'src' => $this->getVar( 'wgRightsIcon' ) ) ) .
971                         '&#160;&#160;' .
972                         htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
973                         "</p>\n" .
974                         "<p style=\"text-align: center\">" .
975                         Html::element( 'a',
976                                 array(
977                                         'href' => $this->getCCPartnerUrl(),
978                                         'onclick' => $expandJs,
979                                 ),
980                                 wfMsg( 'config-cc-again' )
981                         ) .
982                         "</p>\n" .
983                         "<script type=\"text/javascript\">\n" .
984                         # Reduce the wrapper div height
985                         htmlspecialchars( $reduceJs ) .
986                         "\n" .
987                         "</script>\n";
988         }
989
990         public function submitCC() {
991                 $newValues = $this->parent->setVarsFromRequest(
992                         array( 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ) );
993                 if ( count( $newValues ) != 3 ) {
994                         $this->parent->showError( 'config-cc-error' );
995                         return;
996                 }
997                 $this->setVar( '_CCDone', true );
998                 $this->addHTML( $this->getCCDoneBox() );
999         }
1000
1001         public function submit() {
1002                 $this->parent->setVarsFromRequest( array( '_RightsProfile', '_LicenseCode',
1003                         'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', 'wgLogo',
1004                         'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
1005                         'wgEmailAuthentication', 'wgMainCacheType', '_MemCachedServers',
1006                         'wgUseInstantCommons' ) );
1007
1008                 if ( !in_array( $this->getVar( '_RightsProfile' ),
1009                         array_keys( $this->parent->rightsProfiles ) ) )
1010                 {
1011                         reset( $this->parent->rightsProfiles );
1012                         $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
1013                 }
1014
1015                 $code = $this->getVar( '_LicenseCode' );
1016                 if ( $code == 'cc-choose' ) {
1017                         if ( !$this->getVar( '_CCDone' ) ) {
1018                                 $this->parent->showError( 'config-cc-not-chosen' );
1019                                 return false;
1020                         }
1021                 } elseif ( in_array( $code, array_keys( $this->parent->licenses ) ) ) {
1022                         $entry = $this->parent->licenses[$code];
1023                         if ( isset( $entry['text'] ) ) {
1024                                 $this->setVar( 'wgRightsText', $entry['text'] );
1025                         } else {
1026                                 $this->setVar( 'wgRightsText', wfMsg( 'config-license-' . $code ) );
1027                         }
1028                         $this->setVar( 'wgRightsUrl', $entry['url'] );
1029                         $this->setVar( 'wgRightsIcon', $entry['icon'] );
1030                 } else {
1031                         $this->setVar( 'wgRightsText', '' );
1032                         $this->setVar( 'wgRightsUrl', '' );
1033                         $this->setVar( 'wgRightsIcon', '' );
1034                 }
1035
1036                 $extsAvailable = $this->parent->findExtensions();
1037                 $extsToInstall = array();
1038                 foreach( $extsAvailable as $ext ) {
1039                         if( $this->parent->request->getCheck( 'config_ext-' . $ext ) ) {
1040                                 $extsToInstall[] = $ext;
1041                         }
1042                 }
1043                 $this->parent->setVar( '_Extensions', $extsToInstall );
1044
1045                 if( $this->getVar( 'wgMainCacheType' ) == 'memcached' ) {
1046                         $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
1047                         if( !$memcServers ) {
1048                                 $this->parent->showError( 'config-memcache-needservers' );
1049                                 return false;
1050                         }
1051
1052                         foreach( $memcServers as $server ) {
1053                                 $memcParts = explode( ":", $server );
1054                                 if( !IP::isValid( $memcParts[0] ) ) {
1055                                         $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
1056                                         return false;
1057                                 } elseif( !isset( $memcParts[1] )  ) {
1058                                         $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
1059                                         return false;
1060                                 } elseif( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
1061                                         $this->parent->showError( 'config-memcache-badport', 1, 65535 );
1062                                         return false;
1063                                 }
1064                         }
1065                 }
1066                 return true;
1067         }
1068
1069 }
1070
1071 class WebInstaller_Install extends WebInstallerPage {
1072
1073         public function execute() {
1074                 if( $this->getVar( '_UpgradeDone' ) ) {
1075                         return 'skip';
1076                 } elseif( $this->getVar( '_InstallDone' ) ) {
1077                         return 'continue';
1078                 } elseif( $this->parent->request->wasPosted() ) {
1079                         $this->startForm();
1080                         $this->addHTML("<ul>");
1081                         $results = $this->parent->performInstallation(
1082                                 array( $this, 'startStage'),
1083                                 array( $this, 'endStage' )
1084                         );
1085                         $this->addHTML("</ul>");
1086                         // PerformInstallation bails on a fatal, so make sure the last item
1087                         // completed before giving 'next.' Likewise, only provide back on failure
1088                         $lastStep = end( $results );
1089                         $continue = $lastStep->isOK() ? 'continue' : false;
1090                         $back = $lastStep->isOK() ? false : 'back';
1091                         $this->endForm( $continue, $back );
1092                 } else {
1093                         $this->startForm();
1094                         $this->addHTML( $this->parent->getInfoBox( wfMsgNoTrans( 'config-install-begin' ) ) );
1095                         $this->endForm();
1096                 }
1097                 return true;
1098         }
1099
1100         public function startStage( $step ) {
1101                 $this->addHTML( "<li>" . wfMsgHtml( "config-install-$step" ) . wfMsg( 'ellipsis') );
1102                 if ( $step == 'extension-tables' ) {
1103                         $this->startLiveBox();
1104                 }
1105         }
1106
1107         public function endStage( $step, $status ) {
1108                 if ( $step == 'extension-tables' ) {
1109                         $this->endLiveBox();
1110                 }
1111                 $msg = $status->isOk() ? 'config-install-step-done' : 'config-install-step-failed';
1112                 $html = wfMsgHtml( 'word-separator' ) . wfMsgHtml( $msg );
1113                 if ( !$status->isOk() ) {
1114                         $html = "<span class=\"error\">$html</span>";
1115                 }
1116                 $this->addHTML( $html . "</li>\n" );
1117                 if( !$status->isGood() ) {
1118                         $this->parent->showStatusBox( $status );
1119                 }
1120         }
1121
1122 }
1123
1124 class WebInstaller_Complete extends WebInstallerPage {
1125
1126         public function execute() {
1127                 // Pop up a dialog box, to make it difficult for the user to forget
1128                 // to download the file
1129                 $lsUrl = $GLOBALS['wgServer'] . $this->parent->getURL( array( 'localsettings' => 1 ) );
1130                 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) {
1131                         // JS appears the only method that works consistently with IE7+
1132                         $this->addHtml( "\n<script type=\"" . $GLOBALS['wgJsMimeType'] . '">jQuery( document ).ready( function() { document.location='
1133                                 . Xml::encodeJsVar( $lsUrl) . "; } );</script>\n" );
1134                 } else {
1135                         $this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
1136                 }
1137
1138                 $this->startForm();
1139                 $this->parent->disableLinkPopups();
1140                 $this->addHTML(
1141                         $this->parent->getInfoBox(
1142                                 wfMsgNoTrans( 'config-install-done',
1143                                         $lsUrl,
1144                                         $GLOBALS['wgServer'] .
1145                                                 $this->getVar( 'wgScriptPath' ) . '/index' .
1146                                                 $this->getVar( 'wgScriptExtension' ),
1147                                         '<downloadlink/>'
1148                                 ), 'tick-32.png'
1149                         )
1150                 );
1151                 $this->parent->restoreLinkPopups();
1152                 $this->endForm( false, false );
1153         }
1154 }
1155
1156 class WebInstaller_Restart extends WebInstallerPage {
1157
1158         public function execute() {
1159                 $r = $this->parent->request;
1160                 if ( $r->wasPosted() ) {
1161                         $really = $r->getVal( 'submit-restart' );
1162                         if ( $really ) {
1163                                 $this->parent->reset();
1164                         }
1165                         return 'continue';
1166                 }
1167
1168                 $this->startForm();
1169                 $s = $this->parent->getWarningBox( wfMsgNoTrans( 'config-help-restart' ) );
1170                 $this->addHTML( $s );
1171                 $this->endForm( 'restart' );
1172         }
1173
1174 }
1175
1176 abstract class WebInstaller_Document extends WebInstallerPage {
1177
1178         protected abstract function getFileName();
1179
1180         public  function execute() {
1181                 $text = $this->getFileContents();
1182                 $text = $this->formatTextFile( $text );
1183                 $this->parent->output->addWikiText( $text );
1184                 $this->startForm();
1185                 $this->endForm( false );
1186         }
1187
1188         public  function getFileContents() {
1189                 return file_get_contents( dirname( __FILE__ ) . '/../../' . $this->getFileName() );
1190         }
1191
1192         protected function formatTextFile( $text ) {
1193                 // Use Unix line endings, escape some wikitext stuff
1194                 $text = str_replace( array( '<', '{{', '[[', "\r" ),
1195                         array( '&lt;', '&#123;&#123;', '&#91;&#91;', '' ), $text );
1196                 // join word-wrapped lines into one
1197                 do {
1198                         $prev = $text;
1199                         $text = preg_replace( "/\n([\\*#\t])([^\n]*?)\n([^\n#\\*:]+)/", "\n\\1\\2 \\3", $text );
1200                 } while ( $text != $prev );
1201                 // Replace tab indents with colons
1202                 $text = preg_replace( '/^\t\t/m', '::', $text );
1203                 $text = preg_replace( '/^\t/m', ':', $text );
1204                 // turn (bug nnnn) into links
1205                 $text = preg_replace_callback('/bug (\d+)/', array( $this, 'replaceBugLinks' ), $text );
1206                 // add links to manual to every global variable mentioned
1207                 $text = preg_replace_callback('/(\$wg[a-z0-9_]+)/i', array( $this, 'replaceConfigLinks' ), $text );
1208                 return $text;
1209         }
1210
1211         private function replaceBugLinks( $matches ) {
1212                 return '<span class="config-plainlink">[https://bugzilla.wikimedia.org/' .
1213                         $matches[1] . ' bug ' . $matches[1] . ']</span>';
1214         }
1215
1216         private function replaceConfigLinks( $matches ) {
1217                 return '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:' .
1218                         $matches[1] . ' ' . $matches[1] . ']</span>';
1219         }
1220
1221 }
1222
1223 class WebInstaller_Readme extends WebInstaller_Document {
1224         protected function getFileName() { return 'README'; }
1225 }
1226
1227 class WebInstaller_ReleaseNotes extends WebInstaller_Document {
1228         protected function getFileName() { return 'RELEASE-NOTES'; }
1229 }
1230
1231 class WebInstaller_UpgradeDoc extends WebInstaller_Document {
1232         protected function getFileName() { return 'UPGRADE'; }
1233 }
1234
1235 class WebInstaller_Copying extends WebInstaller_Document {
1236         protected function getFileName() { return 'COPYING'; }
1237 }
1238