wiki.techinc.nl/includes/installer/WebInstallerOptions.php
daniel b3b70624c9 Authority: expose user block info
Expose info about user blocks from Authority. This allows calling code
to provide more detailed information to the user about why they are
denied some action on the wiki.

Bug: T271494
Change-Id: Ia84e469888866d72752aad355292666c31e12bad
2021-06-30 13:42:21 +02:00

585 lines
19 KiB
PHP

<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Installer
*/
use Wikimedia\IPUtils;
class WebInstallerOptions extends WebInstallerPage {
/**
* @return string|null
*/
public function execute() {
global $wgLang;
if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
$this->submitSkins();
return 'skip';
}
if ( $this->parent->request->wasPosted() && $this->submit() ) {
return 'continue';
}
$emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
$this->startForm();
$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, config-license-cc-choose
$this->parent->getRadioSet( [
'var' => '_LicenseCode',
'label' => 'config-license',
'itemLabelPrefix' => 'config-license-',
'values' => array_keys( $this->parent->licenses ),
'commonAttribs' => [ 'class' => 'licenseRadio' ],
] ) .
$this->getCCChooser() .
$this->parent->getHelpBox( 'config-license-help' ) .
# E-mail
$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' ) .
"<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
$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' ) .
"</div>" .
$this->getFieldsetEnd()
);
$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 .=
'<div class="config-skins-item">' .
// @phan-suppress-next-line SecurityCheck-DoubleEscaped screenshotText is safe
$this->parent->getCheckBox( [
'var' => "skin-$skin",
'rawtext' => $screenshotText,
'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
] ) .
'<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
'</div>';
}
} else {
$skinHtml .=
Html::warningBox( wfMessage( 'config-skins-missing' )->plain(), 'config-warning-box' ) .
Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
}
$skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
$this->getFieldsetEnd();
$this->addHTML( $skinHtml );
$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 ) {
$urlText = '';
if ( isset( $info['url'] ) ) {
$urlText = ' ' . Html::element( 'a', [ 'href' => $info['url'] ], '(more information)' );
}
$attribs = [
'data-name' => $ext,
'class' => 'config-ext-input'
];
$labelAttribs = [];
$fullDepList = [];
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'] ) ) {
foreach ( $dependencyMap[$ext]['skins'] as $name ) {
$links[] = Html::element(
'a',
[ 'href' => "#config_skin-$name" ],
$name
);
}
}
// @phan-suppress-next-line SecurityCheck-XSS
$text = wfMessage( 'config-extensions-requires' )
->rawParams( $ext, $wgLang->commaList( $links ) )
->escaped();
} else {
$text = $ext;
}
// @phan-suppress-next-line SecurityCheck-DoubleEscaped False positive
$extHtml .= $this->parent->getCheckBox( [
'var' => "ext-$ext",
'rawtext' => $text,
'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 = ' . Xml::encodeJsVar( $dependencyMap )
) );
}
// 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' ) .
// @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
$this->parent->getCheckBox( [
'var' => 'wgEnableUploads',
'label' => 'config-upload-enable',
'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ],
'help' => $this->parent->getHelpBox( 'config-upload-help' )
] ) .
'<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
// @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
$this->parent->getTextBox( [
'var' => 'wgDeletedDirectory',
'label' => 'config-upload-deleted',
'attribs' => [ 'dir' => 'ltr' ],
'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
] ) .
'</div>' .
// @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
$this->parent->getTextBox( [
'var' => '_Logo',
'label' => 'config-logo',
'attribs' => [ 'dir' => 'ltr' ],
'help' => $this->parent->getHelpBox( 'config-logo-help' )
] )
);
$this->addHTML(
// @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
$this->parent->getCheckBox( [
'var' => 'wgUseInstantCommons',
'label' => 'config-instantcommons',
'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
] ) .
$this->getFieldsetEnd()
);
$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' ) .
"<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
// @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
$this->parent->getTextArea( [
'var' => '_MemCachedServers',
'label' => 'config-memcached-servers',
'help' => $this->parent->getHelpBox( 'config-memcached-help' )
] ) .
'</div>' .
$this->getFieldsetEnd()
);
$this->endForm();
return null;
}
/**
* @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();
}
}
/**
* @return string
*/
public function getCCPartnerUrl() {
$server = $this->getVar( 'wgServer' );
$exitUrl = $server . $this->parent->getUrl( [
'page' => 'Options',
'SubmitCC' => 'indeed',
'config__LicenseCode' => 'cc',
'config_wgRightsUrl' => '[license_url]',
'config_wgRightsText' => '[license_name]',
'config_wgRightsIcon' => '[license_button]',
] );
$styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
'/mw-config/config-cc.css';
$iframeUrl = 'https://creativecommons.org/license/?' .
wfArrayToCgi( [
'partner' => 'MediaWiki',
'exit_url' => $exitUrl,
'lang' => $this->getVar( '_UserLang' ),
'stylesheet' => $styleUrl,
] );
return $iframeUrl;
}
/**
* @return string
*/
public function getCCChooser() {
$iframeAttribs = [
'class' => 'config-cc-iframe',
'name' => 'config-cc-iframe',
'id' => 'config-cc-iframe',
'frameborder' => 0,
'width' => '100%',
'height' => '100%',
];
if ( $this->getVar( '_CCDone' ) ) {
$iframeAttribs['src'] = $this->parent->getUrl( [ 'ShowCC' => 'yes' ] );
} else {
$iframeAttribs['src'] = $this->getCCPartnerUrl();
}
$wrapperStyle = ( $this->getVar( '_LicenseCode' ) == 'cc-choose' ) ? '' : 'display: none';
return "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
Html::element( 'iframe', $iframeAttribs ) .
"</div>\n";
}
/**
* @return string
*/
public function getCCDoneBox() {
$js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
// If you change this height, also change it in config.css
$expandJs = str_replace( '$1', '54em', $js );
$reduceJs = str_replace( '$1', '70px', $js );
return '<p>' .
Html::element( 'img', [ 'src' => $this->getVar( 'wgRightsIcon' ) ] ) .
"\u{00A0}\u{00A0}" .
htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
"</p>\n" .
"<p style=\"text-align: center;\">" .
Html::element( 'a',
[
'href' => $this->getCCPartnerUrl(),
'onclick' => $expandJs,
],
wfMessage( 'config-cc-again' )->text()
) .
"</p>\n" .
"<script>\n" .
# Reduce the wrapper div height
htmlspecialchars( $reduceJs ) .
"\n" .
"</script>\n";
}
public function submitCC() {
$newValues = $this->parent->setVarsFromRequest(
[ 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ] );
if ( count( $newValues ) != 3 ) {
$this->parent->showError( 'config-cc-error' );
return;
}
$this->setVar( '_CCDone', true );
$this->addHTML( $this->getCCDoneBox() );
}
/**
* 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', '_Logo',
'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers',
'wgUseInstantCommons', 'wgDefaultSkin' ] );
$retVal = true;
if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
reset( $this->parent->rightsProfiles );
$this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
}
// If this is empty, either the default got lost internally
// or the user blanked it
if ( strval( $this->getVar( '_Logo' ) ) === '' ) {
$this->parent->showError( 'config-install-logo-blank' );
$retVal = false;
}
$code = $this->getVar( '_LicenseCode' );
if ( $code == 'cc-choose' ) {
if ( !$this->getVar( '_CCDone' ) ) {
$this->parent->showError( 'config-cc-not-chosen' );
$retVal = false;
}
} elseif ( 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,
// config-license-cc-choose
$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 && array_search( $defaultSkin, $skinsToInstallLowercase ) === false ) {
$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' ) );
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;
}
}