" message * * Add a "WebInstaller_" class * @var array */ public $pageSequence = array( 'Language', 'ExistingWiki', 'Welcome', 'DBConnect', 'Upgrade', 'DBSettings', 'Name', 'Options', 'Install', 'Complete', ); /** * Out of sequence pages, selectable by the user at any time. * @var array */ protected $otherPages = array( 'Restart', 'Readme', 'ReleaseNotes', 'Copying', 'UpgradeDoc', // Can't use Upgrade due to Upgrade step ); /** * Array of pages which have declared that they have been submitted, have validated * their input, and need no further processing. * @var array */ protected $happyPages; /** * List of "skipped" pages. These are pages that will automatically continue * to the next page on any GET request. To avoid breaking the "back" button, * they need to be skipped during a back operation. * @var array */ protected $skippedPages; /** * Flag indicating that session data may have been lost. * @var bool */ public $showSessionWarning = false; /** * Numeric index of the page we're on * @var int */ protected $tabIndex = 1; /** * Name of the page we're on * @var string */ protected $currentPageName; /** * Constructor. * * @param $request WebRequest */ public function __construct( WebRequest $request ) { parent::__construct(); $this->output = new WebInstallerOutput( $this ); $this->request = $request; // Add parser hooks global $wgParser; $wgParser->setHook( 'downloadlink', array( $this, 'downloadLinkHook' ) ); $wgParser->setHook( 'doclink', array( $this, 'docLink' ) ); } /** * Main entry point. * * @param array $session initial session array * * @return Array: new session array */ public function execute( array $session ) { $this->session = $session; if ( isset( $session['settings'] ) ) { $this->settings = $session['settings'] + $this->settings; } $this->exportVars(); $this->setupLanguage(); if ( ( $this->getVar( '_InstallDone' ) || $this->getVar( '_UpgradeDone' ) ) && $this->request->getVal( 'localsettings' ) ) { $this->request->response()->header( 'Content-type: application/x-httpd-php' ); $this->request->response()->header( 'Content-Disposition: attachment; filename="LocalSettings.php"' ); $ls = InstallerOverrides::getLocalSettingsGenerator( $this ); $rightsProfile = $this->rightsProfiles[$this->getVar( '_RightsProfile' )]; foreach ( $rightsProfile as $group => $rightsArr ) { $ls->setGroupRights( $group, $rightsArr ); } echo $ls->getText(); return $this->session; } $cssDir = $this->request->getVal( 'css' ); if ( $cssDir ) { $cssDir = ( $cssDir == 'rtl' ? 'rtl' : 'ltr' ); $this->request->response()->header( 'Content-type: text/css' ); echo $this->output->getCSS( $cssDir ); return $this->session; } if ( isset( $session['happyPages'] ) ) { $this->happyPages = $session['happyPages']; } else { $this->happyPages = array(); } if ( isset( $session['skippedPages'] ) ) { $this->skippedPages = $session['skippedPages']; } else { $this->skippedPages = array(); } $lowestUnhappy = $this->getLowestUnhappy(); # Special case for Creative Commons partner chooser box. if ( $this->request->getVal( 'SubmitCC' ) ) { $page = $this->getPageByName( 'Options' ); $this->output->useShortHeader(); $this->output->allowFrames(); $page->submitCC(); return $this->finish(); } if ( $this->request->getVal( 'ShowCC' ) ) { $page = $this->getPageByName( 'Options' ); $this->output->useShortHeader(); $this->output->allowFrames(); $this->output->addHTML( $page->getCCDoneBox() ); return $this->finish(); } # Get the page name. $pageName = $this->request->getVal( 'page' ); if ( in_array( $pageName, $this->otherPages ) ) { # Out of sequence $pageId = false; $page = $this->getPageByName( $pageName ); } else { # Main sequence if ( !$pageName || !in_array( $pageName, $this->pageSequence ) ) { $pageId = $lowestUnhappy; } else { $pageId = array_search( $pageName, $this->pageSequence ); } # If necessary, move back to the lowest-numbered unhappy page if ( $pageId > $lowestUnhappy ) { $pageId = $lowestUnhappy; if ( $lowestUnhappy == 0 ) { # Knocked back to start, possible loss of session data. $this->showSessionWarning = true; } } $pageName = $this->pageSequence[$pageId]; $page = $this->getPageByName( $pageName ); } # If a back button was submitted, go back without submitting the form data. if ( $this->request->wasPosted() && $this->request->getBool( 'submit-back' ) ) { if ( $this->request->getVal( 'lastPage' ) ) { $nextPage = $this->request->getVal( 'lastPage' ); } elseif ( $pageId !== false ) { # Main sequence page # Skip the skipped pages $nextPageId = $pageId; do { $nextPageId--; $nextPage = $this->pageSequence[$nextPageId]; } while ( isset( $this->skippedPages[$nextPage] ) ); } else { $nextPage = $this->pageSequence[$lowestUnhappy]; } $this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) ); return $this->finish(); } # Execute the page. $this->currentPageName = $page->getName(); $this->startPageWrapper( $pageName ); if ( $page->isSlow() ) { $this->disableTimeLimit(); } $result = $page->execute(); $this->endPageWrapper(); if ( $result == 'skip' ) { # Page skipped without explicit submission. # Skip it when we click "back" so that we don't just go forward again. $this->skippedPages[$pageName] = true; $result = 'continue'; } else { unset( $this->skippedPages[$pageName] ); } # If it was posted, the page can request a continue to the next page. if ( $result === 'continue' && !$this->output->headerDone() ) { if ( $pageId !== false ) { $this->happyPages[$pageId] = true; } $lowestUnhappy = $this->getLowestUnhappy(); if ( $this->request->getVal( 'lastPage' ) ) { $nextPage = $this->request->getVal( 'lastPage' ); } elseif ( $pageId !== false ) { $nextPage = $this->pageSequence[$pageId + 1]; } else { $nextPage = $this->pageSequence[$lowestUnhappy]; } if ( array_search( $nextPage, $this->pageSequence ) > $lowestUnhappy ) { $nextPage = $this->pageSequence[$lowestUnhappy]; } $this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) ); } return $this->finish(); } /** * Find the next page in sequence that hasn't been completed * @return int */ public function getLowestUnhappy() { if ( count( $this->happyPages ) == 0 ) { return 0; } else { return max( array_keys( $this->happyPages ) ) + 1; } } /** * Start the PHP session. This may be called before execute() to start the PHP session. * * @return bool */ public function startSession() { if ( wfIniGetBool( 'session.auto_start' ) || session_id() ) { // Done already return true; } $this->phpErrors = array(); set_error_handler( array( $this, 'errorHandler' ) ); session_start(); restore_error_handler(); if ( $this->phpErrors ) { $this->showError( 'config-session-error', $this->phpErrors[0] ); return false; } return true; } /** * Get a hash of data identifying this MW installation. * * This is used by mw-config/index.php to prevent multiple installations of MW * on the same cookie domain from interfering with each other. * * @return string */ public function getFingerprint() { // Get the base URL of the installation $url = $this->request->getFullRequestURL(); if ( preg_match( '!^(.*\?)!', $url, $m ) ) { // Trim query string $url = $m[1]; } if ( preg_match( '!^(.*)/[^/]*/[^/]*$!', $url, $m ) ) { // This... seems to try to get the base path from // the /mw-config/index.php. Kinda scary though? $url = $m[1]; } return md5( serialize( array( 'local path' => dirname( __DIR__ ), 'url' => $url, 'version' => $GLOBALS['wgVersion'] ) ) ); } /** * Show an error message in a box. Parameters are like wfMessage(). * @param $msg */ public function showError( $msg /*...*/ ) { $args = func_get_args(); array_shift( $args ); $args = array_map( 'htmlspecialchars', $args ); $msg = wfMessage( $msg, $args )->useDatabase( false )->plain(); $this->output->addHTML( $this->getErrorBox( $msg ) ); } /** * Temporary error handler for session start debugging. * @param $errno * @param $errstr string */ public function errorHandler( $errno, $errstr ) { $this->phpErrors[] = $errstr; } /** * Clean up from execute() * * @return array */ public function finish() { $this->output->output(); $this->session['happyPages'] = $this->happyPages; $this->session['skippedPages'] = $this->skippedPages; $this->session['settings'] = $this->settings; return $this->session; } /** * We're restarting the installation, reset the session, happyPages, etc */ public function reset() { $this->session = array(); $this->happyPages = array(); $this->settings = array(); } /** * Get a URL for submission back to the same script. * * @param $query array * @return string */ public function getUrl( $query = array() ) { $url = $this->request->getRequestURL(); # Remove existing query $url = preg_replace( '/\?.*$/', '', $url ); if ( $query ) { $url .= '?' . wfArrayToCgi( $query ); } return $url; } /** * Get a WebInstallerPage by name. * * @param $pageName String * @return WebInstallerPage */ public function getPageByName( $pageName ) { $pageClass = 'WebInstaller_' . $pageName; return new $pageClass( $this ); } /** * Get a session variable. * * @param $name String * @param $default * @return null */ public function getSession( $name, $default = null ) { if ( !isset( $this->session[$name] ) ) { return $default; } else { return $this->session[$name]; } } /** * Set a session variable. * @param string $name key for the variable * @param $value Mixed */ public function setSession( $name, $value ) { $this->session[$name] = $value; } /** * Get the next tabindex attribute value. * @return int */ public function nextTabIndex() { return $this->tabIndex++; } /** * Initializes language-related variables. */ public function setupLanguage() { global $wgLang, $wgContLang, $wgLanguageCode; if ( $this->getSession( 'test' ) === null && !$this->request->wasPosted() ) { $wgLanguageCode = $this->getAcceptLanguage(); $wgLang = $wgContLang = Language::factory( $wgLanguageCode ); $this->setVar( 'wgLanguageCode', $wgLanguageCode ); $this->setVar( '_UserLang', $wgLanguageCode ); } else { $wgLanguageCode = $this->getVar( 'wgLanguageCode' ); $wgContLang = Language::factory( $wgLanguageCode ); } } /** * Retrieves MediaWiki language from Accept-Language HTTP header. * * @return string */ public function getAcceptLanguage() { global $wgLanguageCode, $wgRequest; $mwLanguages = Language::fetchLanguageNames(); $headerLanguages = array_keys( $wgRequest->getAcceptLang() ); foreach ( $headerLanguages as $lang ) { if ( isset( $mwLanguages[$lang] ) ) { return $lang; } } return $wgLanguageCode; } /** * Called by execute() before page output starts, to show a page list. * * @param $currentPageName string */ private function startPageWrapper( $currentPageName ) { $s = "
\n"; $s .= "
\n"; $s .= "
    \n"; $lastHappy = -1; foreach ( $this->pageSequence as $id => $pageName ) { $happy = !empty( $this->happyPages[$id] ); $s .= $this->getPageListItem( $pageName, $happy || $lastHappy == $id - 1, $currentPageName ); if ( $happy ) { $lastHappy = $id; } } $s .= "

    \n"; $s .= $this->getPageListItem( 'Restart', true, $currentPageName ); // End list pane $s .= "
\n"; // Messages: // config-page-language, config-page-welcome, config-page-dbconnect, config-page-upgrade, // config-page-dbsettings, config-page-name, config-page-options, config-page-install, // config-page-complete, config-page-restart, config-page-readme, config-page-releasenotes, // config-page-copying, config-page-upgradedoc, config-page-existingwiki $s .= Html::element( 'h2', array(), wfMessage( 'config-page-' . strtolower( $currentPageName ) )->text() ); $this->output->addHTMLNoFlush( $s ); } /** * Get a list item for the page list. * * @param $pageName string * @param $enabled boolean * @param $currentPageName string * * @return string */ private function getPageListItem( $pageName, $enabled, $currentPageName ) { $s = "
  • "; // Messages: // config-page-language, config-page-welcome, config-page-dbconnect, config-page-upgrade, // config-page-dbsettings, config-page-name, config-page-options, config-page-install, // config-page-complete, config-page-restart, config-page-readme, config-page-releasenotes, // config-page-copying, config-page-upgradedoc, config-page-existingwiki $name = wfMessage( 'config-page-' . strtolower( $pageName ) )->text(); if ( $enabled ) { $query = array( 'page' => $pageName ); if ( !in_array( $pageName, $this->pageSequence ) ) { if ( in_array( $currentPageName, $this->pageSequence ) ) { $query['lastPage'] = $currentPageName; } $link = Html::element( 'a', array( 'href' => $this->getUrl( $query ) ), $name ); } else { $link = htmlspecialchars( $name ); } if ( $pageName == $currentPageName ) { $s .= "$link"; } else { $s .= $link; } } else { $s .= Html::element( 'span', array( 'class' => 'config-page-disabled' ), $name ); } $s .= "
  • \n"; return $s; } /** * Output some stuff after a page is finished. */ private function endPageWrapper() { $this->output->addHTMLNoFlush( "
    \n" . "
    \n" . "
    \n" . "
    " ); } /** * Get HTML for an error box with an icon. * * @param string $text wikitext, get this with wfMessage()->plain() * * @return string */ public function getErrorBox( $text ) { return $this->getInfoBox( $text, 'critical-32.png', 'config-error-box' ); } /** * Get HTML for a warning box with an icon. * * @param string $text wikitext, get this with wfMessage()->plain() * * @return string */ public function getWarningBox( $text ) { return $this->getInfoBox( $text, 'warning-32.png', 'config-warning-box' ); } /** * Get HTML for an info box with an icon. * * @param string $text wikitext, get this with wfMessage()->plain() * @param string $icon icon name, file in skins/common/images * @param string $class additional class name to add to the wrapper div * * @return string */ public function getInfoBox( $text, $icon = false, $class = false ) { $text = $this->parse( $text, true ); $icon = ( $icon == false ) ? '../skins/common/images/info-32.png' : '../skins/common/images/' . $icon; $alt = wfMessage( 'config-information' )->text(); return Html::infoBox( $text, $icon, $alt, $class, false ); } /** * Get small text indented help for a preceding form field. * Parameters like wfMessage(). * * @param $msg * @return string */ public function getHelpBox( $msg /*, ... */ ) { $args = func_get_args(); array_shift( $args ); $args = array_map( 'htmlspecialchars', $args ); $text = wfMessage( $msg, $args )->useDatabase( false )->plain(); $html = $this->parse( $text, true ); return "
    \n" . "" . wfMessage( 'config-help' )->escaped() . "\n" . "" . $html . "\n" . "
    \n"; } /** * Output a help box. * @param string $msg key for wfMessage() */ public function showHelpBox( $msg /*, ... */ ) { $args = func_get_args(); $html = call_user_func_array( array( $this, 'getHelpBox' ), $args ); $this->output->addHTML( $html ); } /** * Show a short informational message. * Output looks like a list. * * @param $msg string */ public function showMessage( $msg /*, ... */ ) { $args = func_get_args(); array_shift( $args ); $html = '
    ' . $this->parse( wfMessage( $msg, $args )->useDatabase( false )->plain() ) . "
    \n"; $this->output->addHTML( $html ); } /** * @param $status Status */ public function showStatusMessage( Status $status ) { $errors = array_merge( $status->getErrorsArray(), $status->getWarningsArray() ); foreach ( $errors as $error ) { call_user_func_array( array( $this, 'showMessage' ), $error ); } } /** * Label a control by wrapping a config-input div around it and putting a * label before it. * * @param $msg * @param $forId * @param $contents * @param $helpData string * @return string */ public function label( $msg, $forId, $contents, $helpData = "" ) { if ( strval( $msg ) == '' ) { $labelText = ' '; } else { $labelText = wfMessage( $msg )->escaped(); } $attributes = array( 'class' => 'config-label' ); if ( $forId ) { $attributes['for'] = $forId; } return "
    \n" . "
    \n" . Xml::tags( 'label', $attributes, $labelText ) . "\n" . $helpData . "
    \n" . "
    \n" . $contents . "
    \n" . "
    \n"; } /** * Get a labelled text box to configure a variable. * * @param $params Array * Parameters are: * var: The variable to be configured (required) * label: The message name for the label (required) * attribs: Additional attributes for the input element (optional) * controlName: The name for the input element (optional) * value: The current value of the variable (optional) * help: The html for the help text (optional) * * @return string */ public function getTextBox( $params ) { if ( !isset( $params['controlName'] ) ) { $params['controlName'] = 'config_' . $params['var']; } if ( !isset( $params['value'] ) ) { $params['value'] = $this->getVar( $params['var'] ); } if ( !isset( $params['attribs'] ) ) { $params['attribs'] = array(); } if ( !isset( $params['help'] ) ) { $params['help'] = ""; } return $this->label( $params['label'], $params['controlName'], Xml::input( $params['controlName'], 30, // intended to be overridden by CSS $params['value'], $params['attribs'] + array( 'id' => $params['controlName'], 'class' => 'config-input-text', 'tabindex' => $this->nextTabIndex() ) ), $params['help'] ); } /** * Get a labelled textarea to configure a variable * * @param $params Array * Parameters are: * var: The variable to be configured (required) * label: The message name for the label (required) * attribs: Additional attributes for the input element (optional) * controlName: The name for the input element (optional) * value: The current value of the variable (optional) * help: The html for the help text (optional) * * @return string */ public function getTextArea( $params ) { if ( !isset( $params['controlName'] ) ) { $params['controlName'] = 'config_' . $params['var']; } if ( !isset( $params['value'] ) ) { $params['value'] = $this->getVar( $params['var'] ); } if ( !isset( $params['attribs'] ) ) { $params['attribs'] = array(); } if ( !isset( $params['help'] ) ) { $params['help'] = ""; } return $this->label( $params['label'], $params['controlName'], Xml::textarea( $params['controlName'], $params['value'], 30, 5, $params['attribs'] + array( 'id' => $params['controlName'], 'class' => 'config-input-text', 'tabindex' => $this->nextTabIndex() ) ), $params['help'] ); } /** * Get a labelled password box to configure a variable. * * Implements password hiding * @param $params Array * Parameters are: * var: The variable to be configured (required) * label: The message name for the label (required) * attribs: Additional attributes for the input element (optional) * controlName: The name for the input element (optional) * value: The current value of the variable (optional) * help: The html for the help text (optional) * * @return string */ public function getPasswordBox( $params ) { if ( !isset( $params['value'] ) ) { $params['value'] = $this->getVar( $params['var'] ); } if ( !isset( $params['attribs'] ) ) { $params['attribs'] = array(); } $params['value'] = $this->getFakePassword( $params['value'] ); $params['attribs']['type'] = 'password'; return $this->getTextBox( $params ); } /** * Get a labelled checkbox to configure a boolean variable. * * @param $params Array * Parameters are: * var: The variable to be configured (required) * label: The message name for the label (required) * attribs: Additional attributes for the input element (optional) * controlName: The name for the input element (optional) * value: The current value of the variable (optional) * help: The html for the help text (optional) * * @return string */ public function getCheckBox( $params ) { if ( !isset( $params['controlName'] ) ) { $params['controlName'] = 'config_' . $params['var']; } if ( !isset( $params['value'] ) ) { $params['value'] = $this->getVar( $params['var'] ); } if ( !isset( $params['attribs'] ) ) { $params['attribs'] = array(); } if ( !isset( $params['help'] ) ) { $params['help'] = ""; } if ( isset( $params['rawtext'] ) ) { $labelText = $params['rawtext']; } elseif ( isset( $params['label'] ) ) { $labelText = $this->parse( wfMessage( $params['label'] )->text() ); } else { $labelText = ""; } return "
    \n" . $params['help'] . "\n" . "
    \n"; } /** * Get a set of labelled radio buttons. * * @param $params Array * Parameters are: * var: The variable to be configured (required) * label: The message name for the label (required) * itemLabelPrefix: The message name prefix for the item labels (required) * values: List of allowed values (required) * itemAttribs: Array of attribute arrays, outer key is the value name (optional) * commonAttribs: Attribute array applied to all items * controlName: The name for the input element (optional) * value: The current value of the variable (optional) * help: The html for the help text (optional) * * @return string */ public function getRadioSet( $params ) { if ( !isset( $params['controlName'] ) ) { $params['controlName'] = 'config_' . $params['var']; } if ( !isset( $params['value'] ) ) { $params['value'] = $this->getVar( $params['var'] ); } if ( !isset( $params['label'] ) ) { $label = ''; } else { $label = $params['label']; } if ( !isset( $params['help'] ) ) { $params['help'] = ""; } $s = "\n"; return $this->label( $label, $params['controlName'], $s, $params['help'] ); } /** * Output an error or warning box using a Status object. * * @param $status Status */ public function showStatusBox( $status ) { if ( !$status->isGood() ) { $text = $status->getWikiText(); if ( $status->isOk() ) { $box = $this->getWarningBox( $text ); } else { $box = $this->getErrorBox( $text ); } $this->output->addHTML( $box ); } } /** * Convenience function to set variables based on form data. * Assumes that variables containing "password" in the name are (potentially * fake) passwords. * * @param $varNames Array * @param string $prefix the prefix added to variables to obtain form names * * @return array */ public function setVarsFromRequest( $varNames, $prefix = 'config_' ) { $newValues = array(); foreach ( $varNames as $name ) { $value = trim( $this->request->getVal( $prefix . $name ) ); $newValues[$name] = $value; if ( $value === null ) { // Checkbox? $this->setVar( $name, false ); } else { if ( stripos( $name, 'password' ) !== false ) { $this->setPassword( $name, $value ); } else { $this->setVar( $name, $value ); } } } return $newValues; } /** * Helper for Installer::docLink() * * @param $page * @return string */ protected function getDocUrl( $page ) { $url = "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); if ( in_array( $this->currentPageName, $this->pageSequence ) ) { $url .= '&lastPage=' . urlencode( $this->currentPageName ); } return $url; } /** * Extension tag hook for a documentation link. * * @param $linkText * @param $attribs * @param $parser * @return string */ public function docLink( $linkText, $attribs, $parser ) { $url = $this->getDocUrl( $attribs['href'] ); return '' . htmlspecialchars( $linkText ) . ''; } /** * Helper for "Download LocalSettings" link on WebInstall_Complete * * @param $text * @param $attribs * @param $parser * @return String Html for download link */ public function downloadLinkHook( $text, $attribs, $parser ) { $img = Html::element( 'img', array( 'src' => '../skins/common/images/download-32.png', 'width' => '32', 'height' => '32', ) ); $anchor = Html::rawElement( 'a', array( 'href' => $this->getURL( array( 'localsettings' => 1 ) ) ), $img . ' ' . wfMessage( 'config-download-localsettings' )->parse() ); return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor ); } /** * @return bool */ public function envCheckPath() { // PHP_SELF isn't available sometimes, such as when PHP is CGI but // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME // to get the path to the current script... hopefully it's reliable. SIGH $path = false; if ( !empty( $_SERVER['PHP_SELF'] ) ) { $path = $_SERVER['PHP_SELF']; } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) { $path = $_SERVER['SCRIPT_NAME']; } if ( $path !== false ) { $uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path ); $this->setVar( 'wgScriptPath', $uri ); } else { $this->showError( 'config-no-uri' ); return false; } return parent::envCheckPath(); } protected function envGetDefaultServer() { return WebRequest::detectServer(); } }