MediaWiki master
WebInstallerOptions.php
Go to the documentation of this file.
1<?php
2
9namespace MediaWiki\Installer;
10
13use Wikimedia\IPUtils;
14
16
20 public function execute() {
21 if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
22 $this->submitSkins();
23 return 'skip';
24 }
25 if ( $this->parent->request->wasPosted() && $this->submit() ) {
26 return 'continue';
27 }
28
29 $this->startForm();
30 $this->addModeOptions();
31 $this->addEmailOptions();
32 $this->addSkinOptions();
33 $this->addExtensionOptions();
34 $this->addFileOptions();
35 $this->addPersonalizationOptions();
36 $this->addAdvancedOptions();
37 $this->endForm();
38
39 return null;
40 }
41
42 private function addPersonalizationOptions() {
44 $this->addHTML(
45 $this->getFieldsetStart( 'config-personalization-settings' ) .
46 Html::rawElement( 'div', [
47 'class' => 'config-drag-drop'
48 ], wfMessage( 'config-logo-summary' )->parse() ) .
49 Html::openElement( 'div', [
50 'class' => 'config-personalization-options'
51 ] ) .
52 Html::hidden( 'config_LogoSiteName', $this->getVar( 'wgSitename' ) ) .
54 'var' => '_LogoIcon',
55 // Single quotes are intentional, LocalSettingsGenerator must output this unescaped.
56 'value' => '$wgResourceBasePath/resources/assets/change-your-logo.svg',
57 'label' => 'config-logo-icon',
58 'attribs' => [ 'dir' => 'ltr' ],
59 'help' => $parent->getHelpBox( 'config-logo-icon-help' )
60 ] ) .
62 'var' => '_LogoWordmark',
63 'label' => 'config-logo-wordmark',
64 'attribs' => [ 'dir' => 'ltr' ],
65 'help' => $parent->getHelpBox( 'config-logo-wordmark-help' )
66 ] ) .
68 'var' => '_LogoTagline',
69 'label' => 'config-logo-tagline',
70 'attribs' => [ 'dir' => 'ltr' ],
71 'help' => $parent->getHelpBox( 'config-logo-tagline-help' )
72 ] ) .
74 'var' => '_Logo1x',
75 'label' => 'config-logo-sidebar',
76 'attribs' => [ 'dir' => 'ltr' ],
77 'help' => $parent->getHelpBox( 'config-logo-sidebar-help' )
78 ] ) .
79 Html::openElement( 'div', [
80 'class' => 'logo-preview-area',
81 'data-main-page' => wfMessage( 'config-logo-preview-main' ),
82 'data-filedrop' => wfMessage( 'config-logo-filedrop' )
83 ] ) .
84 Html::closeElement( 'div' ) .
85 Html::closeElement( 'div' ) .
86 $this->getFieldsetEnd()
87 );
88 }
89
93 private function addModeOptions(): void {
94 $this->addHTML(
95 # User Rights
96 // getRadioSet() builds a set of labeled radio buttons.
97 // For grep: The following messages are used as the item labels:
98 // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private
99 $this->parent->getRadioSet( [
100 'var' => '_RightsProfile',
101 'label' => 'config-profile',
102 'itemLabelPrefix' => 'config-profile-',
103 'values' => array_keys( $this->parent->rightsProfiles ),
104 ] ) .
105 $this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) .
106
107 # Licensing
108 // getRadioSet() builds a set of labeled radio buttons.
109 // For grep: The following messages are used as the item labels:
110 // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
111 // config-license-cc-0, config-license-pd, config-license-gfdl,
112 // config-license-none
113 $this->parent->getRadioSet( [
114 'var' => '_LicenseCode',
115 'label' => 'config-license',
116 'itemLabelPrefix' => 'config-license-',
117 'values' => array_keys( $this->parent->licenses ),
118 'commonAttribs' => [ 'class' => 'licenseRadio' ],
119 ] ) .
120 $this->parent->getHelpBox( 'config-license-help' )
121 );
122 }
123
127 private function addEmailOptions(): void {
128 $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
129 $this->addHTML(
130 $this->getFieldsetStart( 'config-email-settings' ) .
131 $this->parent->getCheckBox( [
132 'var' => 'wgEnableEmail',
133 'label' => 'config-enable-email',
134 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ],
135 ] ) .
136 $this->parent->getHelpBox( 'config-enable-email-help' ) .
137 "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
138 $this->parent->getTextBox( [
139 'var' => 'wgPasswordSender',
140 'label' => 'config-email-sender'
141 ] ) .
142 $this->parent->getHelpBox( 'config-email-sender-help' ) .
143 $this->parent->getCheckBox( [
144 'var' => 'wgEnableUserEmail',
145 'label' => 'config-email-user',
146 ] ) .
147 $this->parent->getHelpBox( 'config-email-user-help' ) .
148 $this->parent->getCheckBox( [
149 'var' => 'wgEnotifUserTalk',
150 'label' => 'config-email-usertalk',
151 ] ) .
152 $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
153 $this->parent->getCheckBox( [
154 'var' => 'wgEnotifWatchlist',
155 'label' => 'config-email-watchlist',
156 ] ) .
157 $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
158 $this->parent->getCheckBox( [
159 'var' => 'wgEmailAuthentication',
160 'label' => 'config-email-auth',
161 ] ) .
162 $this->parent->getHelpBox( 'config-email-auth-help' ) .
163 "</div>" .
164 $this->getFieldsetEnd()
165 );
166 }
167
171 private function addSkinOptions(): void {
172 $skins = $this->parent->findExtensions( 'skins' )->value;
173 '@phan-var array[] $skins';
174 $skinHtml = $this->getFieldsetStart( 'config-skins' );
175
176 $skinNames = array_map( 'strtolower', array_keys( $skins ) );
177 $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
178
179 if ( $skins ) {
180 $radioButtons = $this->parent->getRadioElements( [
181 'var' => 'wgDefaultSkin',
182 'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ),
183 'values' => $skinNames,
184 'value' => $chosenSkinName,
185 ] );
186
187 foreach ( $skins as $skin => $info ) {
188 if ( isset( $info['screenshots'] ) ) {
189 $screenshotText = $this->makeScreenshotsLink( $skin, $info['screenshots'] );
190 } else {
191 $screenshotText = htmlspecialchars( $skin );
192 }
193 $skinHtml .=
194 '<div class="config-skins-item">' .
195 $this->parent->getCheckBox( [
196 'var' => "skin-$skin",
197 'rawtext' => $screenshotText . $this->makeMoreInfoLink( $info ),
198 'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
199 ] ) .
200 '<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
201 '</div>';
202 }
203 } else {
204 $skinHtml .=
205 Html::warningBox( wfMessage( 'config-skins-missing' )->parse(), 'config-warning-box' ) .
206 Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
207 }
208
209 $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
210 $this->getFieldsetEnd();
211 $this->addHTML( $skinHtml );
212 }
213
217 private function addExtensionOptions(): void {
218 global $wgLang;
219
220 $extensions = $this->parent->findExtensions()->value;
221 '@phan-var array[] $extensions';
222 $dependencyMap = [];
223
224 if ( $extensions ) {
225 $extHtml = $this->getFieldsetStart( 'config-extensions' );
226
227 $extByType = [];
228 $types = SpecialVersion::getExtensionTypes();
229 // Sort by type first
230 foreach ( $extensions as $ext => $info ) {
231 if ( !isset( $info['type'] ) || !isset( $types[$info['type']] ) ) {
232 // We let extensions normally define custom types, but
233 // since we aren't loading extensions, we'll have to
234 // categorize them under other
235 $info['type'] = 'other';
236 }
237 $extByType[$info['type']][$ext] = $info;
238 }
239
240 foreach ( $types as $type => $message ) {
241 if ( !isset( $extByType[$type] ) ) {
242 continue;
243 }
244 $extHtml .= Html::element( 'h2', [], $message );
245 foreach ( $extByType[$type] as $ext => $info ) {
246 $attribs = [
247 'data-name' => $ext,
248 'class' => 'config-ext-input cdx-checkbox__input'
249 ];
250 $labelAttribs = [];
251 if ( isset( $info['requires']['extensions'] ) ) {
252 $dependencyMap[$ext]['extensions'] = $info['requires']['extensions'];
253 $labelAttribs['class'] = 'mw-ext-with-dependencies';
254 }
255 if ( isset( $info['requires']['skins'] ) ) {
256 $dependencyMap[$ext]['skins'] = $info['requires']['skins'];
257 $labelAttribs['class'] = 'mw-ext-with-dependencies';
258 }
259 if ( isset( $dependencyMap[$ext] ) ) {
260 $links = [];
261 // For each dependency, link to the checkbox for each
262 // extension/skin that is required
263 if ( isset( $dependencyMap[$ext]['extensions'] ) ) {
264 foreach ( $dependencyMap[$ext]['extensions'] as $name ) {
265 $links[] = Html::element(
266 'a',
267 [ 'href' => "#config_ext-$name" ],
268 $name
269 );
270 }
271 }
272 if ( isset( $dependencyMap[$ext]['skins'] ) ) {
273 // @phan-suppress-next-line PhanTypeMismatchForeach Phan internal bug
274 foreach ( $dependencyMap[$ext]['skins'] as $name ) {
275 $links[] = Html::element(
276 'a',
277 [ 'href' => "#config_skin-$name" ],
278 $name
279 );
280 }
281 }
282
283 $text = wfMessage( 'config-extensions-requires', $ext )
284 ->rawParams( $wgLang->commaList( $links ) )
285 ->escaped();
286 } else {
287 $text = htmlspecialchars( $ext );
288 }
289 $extHtml .= $this->parent->getCheckBox( [
290 'var' => "ext-$ext",
291 'rawtext' => $text . $this->makeMoreInfoLink( $info ),
292 'attribs' => $attribs,
293 'labelAttribs' => $labelAttribs,
294 ] );
295 }
296 }
297
298 $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
299 $this->getFieldsetEnd();
300 $this->addHTML( $extHtml );
301 // Push the dependency map to the client side
302 $this->addHTML( $this->inlineScript(
303 'var extDependencyMap = ' . Html::encodeJsVar( $dependencyMap )
304 ) );
305 }
306 }
307
311 private function addFileOptions(): void {
312 // Having / in paths in Windows looks funny :)
313 $this->setVar( 'wgDeletedDirectory',
314 str_replace(
315 '/', DIRECTORY_SEPARATOR,
316 $this->getVar( 'wgDeletedDirectory' )
317 )
318 );
319
320 $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
321 $this->addHTML(
322 # Uploading
323 $this->getFieldsetStart( 'config-upload-settings' ) .
324 $this->parent->getCheckBox( [
325 'var' => 'wgEnableUploads',
326 'label' => 'config-upload-enable',
327 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ],
328 'help' => $this->parent->getHelpBox( 'config-upload-help' )
329 ] ) .
330 '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
331 $this->parent->getTextBox( [
332 'var' => 'wgDeletedDirectory',
333 'label' => 'config-upload-deleted',
334 'attribs' => [ 'dir' => 'ltr' ],
335 'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
336 ] ) .
337 '</div>'
338 );
339 $this->addHTML(
340 $this->parent->getCheckBox( [
341 'var' => 'wgUseInstantCommons',
342 'label' => 'config-instantcommons',
343 'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
344 ] ) .
345 $this->getFieldsetEnd()
346 );
347 }
348
352 private function addAdvancedOptions(): void {
353 $caches = [ 'none' ];
354 $cachevalDefault = 'none';
355
356 if ( count( $this->getVar( '_Caches' ) ) ) {
357 // A CACHE_ACCEL implementation is available
358 $caches[] = 'accel';
359 $cachevalDefault = 'accel';
360 }
361 $caches[] = 'memcached';
362
363 // We'll hide/show this on demand when the value changes, see config.js.
364 $cacheval = $this->getVar( '_MainCacheType' );
365 if ( !$cacheval ) {
366 // We need to set a default here; but don't hardcode it
367 // or we lose it every time we reload the page for validation
368 // or going back!
369 $cacheval = $cachevalDefault;
370 }
371 $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none';
372 $this->addHTML(
373 # Advanced settings
374 $this->getFieldsetStart( 'config-advanced-settings' ) .
375 # Object cache settings
376 // getRadioSet() builds a set of labeled radio buttons.
377 // For grep: The following messages are used as the item labels:
378 // config-cache-none, config-cache-accel, config-cache-memcached
379 $this->parent->getRadioSet( [
380 'var' => '_MainCacheType',
381 'label' => 'config-cache-options',
382 'itemLabelPrefix' => 'config-cache-',
383 'values' => $caches,
384 'value' => $cacheval,
385 ] ) .
386 $this->parent->getHelpBox( 'config-cache-help' ) .
387 "<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
388 $this->parent->getTextArea( [
389 'var' => '_MemCachedServers',
390 'label' => 'config-memcached-servers',
391 'help' => $this->parent->getHelpBox( 'config-memcached-help' )
392 ] ) .
393 '</div>' .
394 $this->getFieldsetEnd()
395 );
396 }
397
403 private function makeScreenshotsLink( $name, $screenshots ) {
404 global $wgLang;
405 if ( count( $screenshots ) > 1 ) {
406 $links = [];
407 $counter = 1;
408
409 foreach ( $screenshots as $shot ) {
410 $links[] = Html::element(
411 'a',
412 [ 'href' => $shot, 'target' => '_blank' ],
413 $wgLang->formatNum( $counter++ )
414 );
415 }
416 return wfMessage( 'config-skins-screenshots', $name )
417 ->rawParams( $wgLang->commaList( $links ) )
418 ->escaped();
419 } else {
420 $link = Html::element(
421 'a',
422 [ 'href' => $screenshots[0], 'target' => '_blank' ],
423 wfMessage( 'config-screenshot' )->text()
424 );
425 return wfMessage( 'config-skins-screenshot', $name )->rawParams( $link )->escaped();
426 }
427 }
428
433 private function makeMoreInfoLink( $info ) {
434 if ( !isset( $info['url'] ) ) {
435 return '';
436 }
437 return ' ' . wfMessage( 'parentheses' )->rawParams(
438 Html::element(
439 'a',
440 [ 'href' => $info['url'] ],
441 wfMessage( 'config-ext-skins-more-info' )->text()
442 )
443 )->escaped();
444 }
445
452 public function submitSkins() {
453 $skins = array_keys( $this->parent->findExtensions( 'skins' )->value );
454 $this->parent->setVar( '_Skins', $skins );
455
456 if ( $skins ) {
457 $skinNames = array_map( 'strtolower', $skins );
458 $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
459 }
460
461 return true;
462 }
463
467 public function submit() {
468 $this->parent->setVarsFromRequest( [ '_RightsProfile', '_LicenseCode',
469 'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads',
470 '_Logo1x', '_LogoWordmark', '_LogoTagline', '_LogoIcon',
471 'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
472 'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers',
473 'wgUseInstantCommons', 'wgDefaultSkin' ] );
474
475 $retVal = true;
476
477 if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
478 $this->setVar( '_RightsProfile', array_key_first( $this->parent->rightsProfiles ) );
479 }
480
481 $code = $this->getVar( '_LicenseCode' );
482 if ( array_key_exists( $code, $this->parent->licenses ) ) {
483 // Messages:
484 // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
485 // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none
486 $entry = $this->parent->licenses[$code];
487 $this->setVar( 'wgRightsText',
488 $entry['text'] ?? wfMessage( 'config-license-' . $code )->text() );
489 $this->setVar( 'wgRightsUrl', $entry['url'] );
490 $this->setVar( 'wgRightsIcon', $entry['icon'] );
491 } else {
492 $this->setVar( 'wgRightsText', '' );
493 $this->setVar( 'wgRightsUrl', '' );
494 $this->setVar( 'wgRightsIcon', '' );
495 }
496
497 $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' )->value );
498 $skinsToInstall = [];
499 foreach ( $skinsAvailable as $skin ) {
500 $this->parent->setVarsFromRequest( [ "skin-$skin" ] );
501 if ( $this->getVar( "skin-$skin" ) ) {
502 $skinsToInstall[] = $skin;
503 }
504 }
505 $this->parent->setVar( '_Skins', $skinsToInstall );
506
507 if ( !$skinsToInstall && $skinsAvailable ) {
508 $this->parent->showError( 'config-skins-must-enable-some' );
509 $retVal = false;
510 }
511 $defaultSkin = $this->getVar( 'wgDefaultSkin' );
512 $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall );
513 if ( $skinsToInstall && !in_array( $defaultSkin, $skinsToInstallLowercase ) ) {
514 $this->parent->showError( 'config-skins-must-enable-default' );
515 $retVal = false;
516 }
517
518 $extsAvailable = array_keys( $this->parent->findExtensions()->value );
519 $extsToInstall = [];
520 foreach ( $extsAvailable as $ext ) {
521 $this->parent->setVarsFromRequest( [ "ext-$ext" ] );
522 if ( $this->getVar( "ext-$ext" ) ) {
523 $extsToInstall[] = $ext;
524 }
525 }
526 $this->parent->setVar( '_Extensions', $extsToInstall );
527
528 if ( $this->getVar( '_MainCacheType' ) == 'memcached' ) {
529 $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
530 // FIXME: explode() will always result in an array of at least one string, even on null (when
531 // the string will be empty and you'll get a PHP warning), so this has never worked?
532 // @phan-suppress-next-line PhanImpossibleCondition
533 if ( !$memcServers ) {
534 $this->parent->showError( 'config-memcache-needservers' );
535 $retVal = false;
536 }
537
538 foreach ( $memcServers as $server ) {
539 $memcParts = explode( ":", $server, 2 );
540 if ( !isset( $memcParts[0] )
541 || ( !IPUtils::isValid( $memcParts[0] )
542 && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
543 ) {
544 $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
545 $retVal = false;
546 } elseif ( !isset( $memcParts[1] ) ) {
547 $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
548 $retVal = false;
549 } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
550 $this->parent->showError( 'config-memcache-badport', 1, 65535 );
551 $retVal = false;
552 }
553 }
554 }
555
556 return $retVal;
557 }
558
559}
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Definition Setup.php:551
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
submitSkins()
If the user skips this installer page, we still need to set up the default skins, but ignore everythi...
Abstract class to define pages for the web installer.
getFieldsetStart( $legend)
Get the starting tags of a fieldset.
endForm( $continue='continue', $back='back')
getFieldsetEnd()
Get the end tag of a fieldset.
WebInstaller $parent
The WebInstaller object this WebInstallerPage belongs to.
getTextBox( $params)
Get a labelled text box to configure a variable.
getHelpBox( $msg,... $params)
Get small text indented help for a preceding form field.
Version information about MediaWiki (core, extensions, libs), PHP, and the database.