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