getVar( '_SkipOptional' ) == 'skip' ) { $this->submitSkins(); return 'skip'; } if ( $this->parent->request->wasPosted() && $this->submit() ) { return 'continue'; } $this->startForm(); $this->addModeOptions(); $this->addEmailOptions(); $this->addSkinOptions(); $this->addExtensionOptions(); $this->addFileOptions(); $this->addPersonalizationOptions(); $this->addAdvancedOptions(); $this->endForm(); return null; } private function addPersonalizationOptions() { $parent = $this->parent; $this->addHTML( $this->getFieldsetStart( 'config-personalization-settings' ) . Html::rawElement( 'div', [ 'class' => 'config-drag-drop' ], wfMessage( 'config-logo-summary' )->parse() ) . Html::openElement( 'div', [ 'class' => 'config-personalization-options' ] ) . Html::hidden( 'config_LogoSiteName', $this->getVar( 'wgSitename' ) ) . $parent->getTextBox( [ 'var' => '_LogoIcon', // Single quotes are intentional, LocalSettingsGenerator must output this unescaped. 'value' => '$wgResourceBasePath/resources/assets/change-your-logo.svg', 'label' => 'config-logo-icon', 'attribs' => [ 'dir' => 'ltr' ], 'help' => $parent->getHelpBox( 'config-logo-icon-help' ) ] ) . $parent->getTextBox( [ 'var' => '_LogoWordmark', 'label' => 'config-logo-wordmark', 'attribs' => [ 'dir' => 'ltr' ], 'help' => $parent->getHelpBox( 'config-logo-wordmark-help' ) ] ) . $parent->getTextBox( [ 'var' => '_LogoTagline', 'label' => 'config-logo-tagline', 'attribs' => [ 'dir' => 'ltr' ], 'help' => $parent->getHelpBox( 'config-logo-tagline-help' ) ] ) . $parent->getTextBox( [ 'var' => '_Logo1x', 'label' => 'config-logo-sidebar', 'attribs' => [ 'dir' => 'ltr' ], 'help' => $parent->getHelpBox( 'config-logo-sidebar-help' ) ] ) . Html::openElement( 'div', [ 'class' => 'logo-preview-area', 'data-main-page' => wfMessage( 'config-logo-preview-main' ), 'data-filedrop' => wfMessage( 'config-logo-filedrop' ) ] ) . Html::closeElement( 'div' ) . Html::closeElement( 'div' ) . $this->getFieldsetEnd() ); } /** * Wiki mode - user rights and copyright model. */ private function addModeOptions(): void { $this->addHTML( # User Rights // getRadioSet() builds a set of labeled radio buttons. // For grep: The following messages are used as the item labels: // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private $this->parent->getRadioSet( [ 'var' => '_RightsProfile', 'label' => 'config-profile', 'itemLabelPrefix' => 'config-profile-', 'values' => array_keys( $this->parent->rightsProfiles ), ] ) . $this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) . # Licensing // getRadioSet() builds a set of labeled radio buttons. // For grep: The following messages are used as the item labels: // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa, // config-license-cc-0, config-license-pd, config-license-gfdl, // config-license-none $this->parent->getRadioSet( [ 'var' => '_LicenseCode', 'label' => 'config-license', 'itemLabelPrefix' => 'config-license-', 'values' => array_keys( $this->parent->licenses ), 'commonAttribs' => [ 'class' => 'licenseRadio' ], ] ) . $this->parent->getHelpBox( 'config-license-help' ) ); } /** * User email options. */ private function addEmailOptions(): void { $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none'; $this->addHTML( $this->getFieldsetStart( 'config-email-settings' ) . $this->parent->getCheckBox( [ 'var' => 'wgEnableEmail', 'label' => 'config-enable-email', 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ], ] ) . $this->parent->getHelpBox( 'config-enable-email-help' ) . "
" . $this->parent->getTextBox( [ 'var' => 'wgPasswordSender', 'label' => 'config-email-sender' ] ) . $this->parent->getHelpBox( 'config-email-sender-help' ) . $this->parent->getCheckBox( [ 'var' => 'wgEnableUserEmail', 'label' => 'config-email-user', ] ) . $this->parent->getHelpBox( 'config-email-user-help' ) . $this->parent->getCheckBox( [ 'var' => 'wgEnotifUserTalk', 'label' => 'config-email-usertalk', ] ) . $this->parent->getHelpBox( 'config-email-usertalk-help' ) . $this->parent->getCheckBox( [ 'var' => 'wgEnotifWatchlist', 'label' => 'config-email-watchlist', ] ) . $this->parent->getHelpBox( 'config-email-watchlist-help' ) . $this->parent->getCheckBox( [ 'var' => 'wgEmailAuthentication', 'label' => 'config-email-auth', ] ) . $this->parent->getHelpBox( 'config-email-auth-help' ) . "
" . $this->getFieldsetEnd() ); } /** * Opt-in for bundled skins. */ private function addSkinOptions(): void { $skins = $this->parent->findExtensions( 'skins' )->value; '@phan-var array[] $skins'; $skinHtml = $this->getFieldsetStart( 'config-skins' ); $skinNames = array_map( 'strtolower', array_keys( $skins ) ); $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) ); if ( $skins ) { $radioButtons = $this->parent->getRadioElements( [ 'var' => 'wgDefaultSkin', 'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ), 'values' => $skinNames, 'value' => $chosenSkinName, ] ); foreach ( $skins as $skin => $info ) { if ( isset( $info['screenshots'] ) ) { $screenshotText = $this->makeScreenshotsLink( $skin, $info['screenshots'] ); } else { $screenshotText = htmlspecialchars( $skin ); } $skinHtml .= '
' . $this->parent->getCheckBox( [ 'var' => "skin-$skin", 'rawtext' => $screenshotText . $this->makeMoreInfoLink( $info ), 'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default ] ) . '
' . $radioButtons[strtolower( $skin )] . '
' . '
'; } } else { $skinHtml .= Html::warningBox( wfMessage( 'config-skins-missing' )->parse(), 'config-warning-box' ) . Html::hidden( 'config_wgDefaultSkin', $chosenSkinName ); } $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) . $this->getFieldsetEnd(); $this->addHTML( $skinHtml ); } /** * Opt-in for bundled extensions. */ private function addExtensionOptions(): void { global $wgLang; $extensions = $this->parent->findExtensions()->value; '@phan-var array[] $extensions'; $dependencyMap = []; if ( $extensions ) { $extHtml = $this->getFieldsetStart( 'config-extensions' ); $extByType = []; $types = SpecialVersion::getExtensionTypes(); // Sort by type first foreach ( $extensions as $ext => $info ) { if ( !isset( $info['type'] ) || !isset( $types[$info['type']] ) ) { // We let extensions normally define custom types, but // since we aren't loading extensions, we'll have to // categorize them under other $info['type'] = 'other'; } $extByType[$info['type']][$ext] = $info; } foreach ( $types as $type => $message ) { if ( !isset( $extByType[$type] ) ) { continue; } $extHtml .= Html::element( 'h2', [], $message ); foreach ( $extByType[$type] as $ext => $info ) { $attribs = [ 'data-name' => $ext, 'class' => 'config-ext-input cdx-checkbox__input' ]; $labelAttribs = []; if ( isset( $info['requires']['extensions'] ) ) { $dependencyMap[$ext]['extensions'] = $info['requires']['extensions']; $labelAttribs['class'] = 'mw-ext-with-dependencies'; } if ( isset( $info['requires']['skins'] ) ) { $dependencyMap[$ext]['skins'] = $info['requires']['skins']; $labelAttribs['class'] = 'mw-ext-with-dependencies'; } if ( isset( $dependencyMap[$ext] ) ) { $links = []; // For each dependency, link to the checkbox for each // extension/skin that is required if ( isset( $dependencyMap[$ext]['extensions'] ) ) { foreach ( $dependencyMap[$ext]['extensions'] as $name ) { $links[] = Html::element( 'a', [ 'href' => "#config_ext-$name" ], $name ); } } if ( isset( $dependencyMap[$ext]['skins'] ) ) { // @phan-suppress-next-line PhanTypeMismatchForeach Phan internal bug foreach ( $dependencyMap[$ext]['skins'] as $name ) { $links[] = Html::element( 'a', [ 'href' => "#config_skin-$name" ], $name ); } } $text = wfMessage( 'config-extensions-requires' ) ->rawParams( $ext, $wgLang->commaList( $links ) ) ->escaped(); } else { $text = $ext; } $extHtml .= $this->parent->getCheckBox( [ 'var' => "ext-$ext", 'rawtext' => $text . $this->makeMoreInfoLink( $info ), 'attribs' => $attribs, 'labelAttribs' => $labelAttribs, ] ); } } $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) . $this->getFieldsetEnd(); $this->addHTML( $extHtml ); // Push the dependency map to the client side $this->addHTML( Html::inlineScript( 'var extDependencyMap = ' . Html::encodeJsVar( $dependencyMap ) ) ); } } /** * Image and file upload options. */ private function addFileOptions(): void { // Having / in paths in Windows looks funny :) $this->setVar( 'wgDeletedDirectory', str_replace( '/', DIRECTORY_SEPARATOR, $this->getVar( 'wgDeletedDirectory' ) ) ); $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none'; $this->addHTML( # Uploading $this->getFieldsetStart( 'config-upload-settings' ) . $this->parent->getCheckBox( [ 'var' => 'wgEnableUploads', 'label' => 'config-upload-enable', 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ], 'help' => $this->parent->getHelpBox( 'config-upload-help' ) ] ) . '
' . $this->parent->getTextBox( [ 'var' => 'wgDeletedDirectory', 'label' => 'config-upload-deleted', 'attribs' => [ 'dir' => 'ltr' ], 'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' ) ] ) . '
' ); $this->addHTML( $this->parent->getCheckBox( [ 'var' => 'wgUseInstantCommons', 'label' => 'config-instantcommons', 'help' => $this->parent->getHelpBox( 'config-instantcommons-help' ) ] ) . $this->getFieldsetEnd() ); } /** * System administration related options. */ private function addAdvancedOptions(): void { $caches = [ 'none' ]; $cachevalDefault = 'none'; if ( count( $this->getVar( '_Caches' ) ) ) { // A CACHE_ACCEL implementation is available $caches[] = 'accel'; $cachevalDefault = 'accel'; } $caches[] = 'memcached'; // We'll hide/show this on demand when the value changes, see config.js. $cacheval = $this->getVar( '_MainCacheType' ); if ( !$cacheval ) { // We need to set a default here; but don't hardcode it // or we lose it every time we reload the page for validation // or going back! $cacheval = $cachevalDefault; } $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none'; $this->addHTML( # Advanced settings $this->getFieldsetStart( 'config-advanced-settings' ) . # Object cache settings // getRadioSet() builds a set of labeled radio buttons. // For grep: The following messages are used as the item labels: // config-cache-none, config-cache-accel, config-cache-memcached $this->parent->getRadioSet( [ 'var' => '_MainCacheType', 'label' => 'config-cache-options', 'itemLabelPrefix' => 'config-cache-', 'values' => $caches, 'value' => $cacheval, ] ) . $this->parent->getHelpBox( 'config-cache-help' ) . "
" . $this->parent->getTextArea( [ 'var' => '_MemCachedServers', 'label' => 'config-memcached-servers', 'help' => $this->parent->getHelpBox( 'config-memcached-help' ) ] ) . '
' . $this->getFieldsetEnd() ); } /** * @param string $name * @param array $screenshots * @return string HTML */ private function makeScreenshotsLink( $name, $screenshots ) { global $wgLang; if ( count( $screenshots ) > 1 ) { $links = []; $counter = 1; foreach ( $screenshots as $shot ) { $links[] = Html::element( 'a', [ 'href' => $shot, 'target' => '_blank' ], $wgLang->formatNum( $counter++ ) ); } return wfMessage( 'config-skins-screenshots' ) ->rawParams( $name, $wgLang->commaList( $links ) ) ->escaped(); } else { $link = Html::element( 'a', [ 'href' => $screenshots[0], 'target' => '_blank' ], wfMessage( 'config-screenshot' )->text() ); return wfMessage( 'config-skins-screenshot', $name )->rawParams( $link )->escaped(); } } /** * @param array $info * @return string HTML */ private function makeMoreInfoLink( $info ) { if ( !isset( $info['url'] ) ) { return ''; } return ' ' . wfMessage( 'parentheses' )->rawParams( Html::element( 'a', [ 'href' => $info['url'] ], wfMessage( 'config-ext-skins-more-info' )->text() ) )->escaped(); } /** * If the user skips this installer page, we still need to set up the default skins, but ignore * everything else. * * @return bool */ public function submitSkins() { $skins = array_keys( $this->parent->findExtensions( 'skins' )->value ); $this->parent->setVar( '_Skins', $skins ); if ( $skins ) { $skinNames = array_map( 'strtolower', $skins ); $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) ); } return true; } /** * @return bool */ public function submit() { $this->parent->setVarsFromRequest( [ '_RightsProfile', '_LicenseCode', 'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', '_Logo1x', '_LogoWordmark', '_LogoTagline', '_LogoIcon', 'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist', 'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers', 'wgUseInstantCommons', 'wgDefaultSkin' ] ); $retVal = true; if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) { $this->setVar( '_RightsProfile', array_key_first( $this->parent->rightsProfiles ) ); } $code = $this->getVar( '_LicenseCode' ); if ( array_key_exists( $code, $this->parent->licenses ) ) { // Messages: // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa, // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none $entry = $this->parent->licenses[$code]; $this->setVar( 'wgRightsText', $entry['text'] ?? wfMessage( 'config-license-' . $code )->text() ); $this->setVar( 'wgRightsUrl', $entry['url'] ); $this->setVar( 'wgRightsIcon', $entry['icon'] ); } else { $this->setVar( 'wgRightsText', '' ); $this->setVar( 'wgRightsUrl', '' ); $this->setVar( 'wgRightsIcon', '' ); } $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' )->value ); $skinsToInstall = []; foreach ( $skinsAvailable as $skin ) { $this->parent->setVarsFromRequest( [ "skin-$skin" ] ); if ( $this->getVar( "skin-$skin" ) ) { $skinsToInstall[] = $skin; } } $this->parent->setVar( '_Skins', $skinsToInstall ); if ( !$skinsToInstall && $skinsAvailable ) { $this->parent->showError( 'config-skins-must-enable-some' ); $retVal = false; } $defaultSkin = $this->getVar( 'wgDefaultSkin' ); $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall ); if ( $skinsToInstall && !in_array( $defaultSkin, $skinsToInstallLowercase ) ) { $this->parent->showError( 'config-skins-must-enable-default' ); $retVal = false; } $extsAvailable = array_keys( $this->parent->findExtensions()->value ); $extsToInstall = []; foreach ( $extsAvailable as $ext ) { $this->parent->setVarsFromRequest( [ "ext-$ext" ] ); if ( $this->getVar( "ext-$ext" ) ) { $extsToInstall[] = $ext; } } $this->parent->setVar( '_Extensions', $extsToInstall ); if ( $this->getVar( '_MainCacheType' ) == 'memcached' ) { $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) ); // FIXME: explode() will always result in an array of at least one string, even on null (when // the string will be empty and you'll get a PHP warning), so this has never worked? // @phan-suppress-next-line PhanImpossibleCondition if ( !$memcServers ) { $this->parent->showError( 'config-memcache-needservers' ); $retVal = false; } foreach ( $memcServers as $server ) { $memcParts = explode( ":", $server, 2 ); if ( !isset( $memcParts[0] ) || ( !IPUtils::isValid( $memcParts[0] ) && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) ) ) { $this->parent->showError( 'config-memcache-badip', $memcParts[0] ); $retVal = false; } elseif ( !isset( $memcParts[1] ) ) { $this->parent->showError( 'config-memcache-noport', $memcParts[0] ); $retVal = false; } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) { $this->parent->showError( 'config-memcache-badport', 1, 65535 ); $retVal = false; } } } return $retVal; } }