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
107 private function addModeOptions(): void {
108 $this->addHTML(
109 # User Rights
110 // getRadioSet() builds a set of labeled radio buttons.
111 // For grep: The following messages are used as the item labels:
112 // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private
113 $this->parent->getRadioSet( [
114 'var' => '_RightsProfile',
115 'label' => 'config-profile',
116 'itemLabelPrefix' => 'config-profile-',
117 'values' => array_keys( $this->parent->rightsProfiles ),
118 ] ) .
119 $this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) .
120
121 # Licensing
122 // getRadioSet() builds a set of labeled radio buttons.
123 // For grep: The following messages are used as the item labels:
124 // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
125 // config-license-cc-0, config-license-pd, config-license-gfdl,
126 // config-license-none
127 $this->parent->getRadioSet( [
128 'var' => '_LicenseCode',
129 'label' => 'config-license',
130 'itemLabelPrefix' => 'config-license-',
131 'values' => array_keys( $this->parent->licenses ),
132 'commonAttribs' => [ 'class' => 'licenseRadio' ],
133 ] ) .
134 $this->parent->getHelpBox( 'config-license-help' )
135 );
136 }
137
141 private function addEmailOptions(): void {
142 $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
143 $this->addHTML(
144 $this->getFieldsetStart( 'config-email-settings' ) .
145 $this->parent->getCheckBox( [
146 'var' => 'wgEnableEmail',
147 'label' => 'config-enable-email',
148 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ],
149 ] ) .
150 $this->parent->getHelpBox( 'config-enable-email-help' ) .
151 "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
152 $this->parent->getTextBox( [
153 'var' => 'wgPasswordSender',
154 'label' => 'config-email-sender'
155 ] ) .
156 $this->parent->getHelpBox( 'config-email-sender-help' ) .
157 $this->parent->getCheckBox( [
158 'var' => 'wgEnableUserEmail',
159 'label' => 'config-email-user',
160 ] ) .
161 $this->parent->getHelpBox( 'config-email-user-help' ) .
162 $this->parent->getCheckBox( [
163 'var' => 'wgEnotifUserTalk',
164 'label' => 'config-email-usertalk',
165 ] ) .
166 $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
167 $this->parent->getCheckBox( [
168 'var' => 'wgEnotifWatchlist',
169 'label' => 'config-email-watchlist',
170 ] ) .
171 $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
172 $this->parent->getCheckBox( [
173 'var' => 'wgEmailAuthentication',
174 'label' => 'config-email-auth',
175 ] ) .
176 $this->parent->getHelpBox( 'config-email-auth-help' ) .
177 "</div>" .
178 $this->getFieldsetEnd()
179 );
180 }
181
185 private function addSkinOptions(): void {
186 $skins = $this->parent->findExtensions( 'skins' )->value;
187 '@phan-var array[] $skins';
188 $skinHtml = $this->getFieldsetStart( 'config-skins' );
189
190 $skinNames = array_map( 'strtolower', array_keys( $skins ) );
191 $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
192
193 if ( $skins ) {
194 $radioButtons = $this->parent->getRadioElements( [
195 'var' => 'wgDefaultSkin',
196 'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ),
197 'values' => $skinNames,
198 'value' => $chosenSkinName,
199 ] );
200
201 foreach ( $skins as $skin => $info ) {
202 if ( isset( $info['screenshots'] ) ) {
203 $screenshotText = $this->makeScreenshotsLink( $skin, $info['screenshots'] );
204 } else {
205 $screenshotText = htmlspecialchars( $skin );
206 }
207 $skinHtml .=
208 '<div class="config-skins-item">' .
209 $this->parent->getCheckBox( [
210 'var' => "skin-$skin",
211 'rawtext' => $screenshotText . $this->makeMoreInfoLink( $info ),
212 'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
213 ] ) .
214 '<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
215 '</div>';
216 }
217 } else {
218 $skinHtml .=
219 Html::warningBox( wfMessage( 'config-skins-missing' )->parse(), 'config-warning-box' ) .
220 Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
221 }
222
223 $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
224 $this->getFieldsetEnd();
225 $this->addHTML( $skinHtml );
226 }
227
231 private function addExtensionOptions(): void {
232 global $wgLang;
233
234 $extensions = $this->parent->findExtensions()->value;
235 '@phan-var array[] $extensions';
236 $dependencyMap = [];
237
238 if ( $extensions ) {
239 $extHtml = $this->getFieldsetStart( 'config-extensions' );
240
241 $extByType = [];
242 $types = SpecialVersion::getExtensionTypes();
243 // Sort by type first
244 foreach ( $extensions as $ext => $info ) {
245 if ( !isset( $info['type'] ) || !isset( $types[$info['type']] ) ) {
246 // We let extensions normally define custom types, but
247 // since we aren't loading extensions, we'll have to
248 // categorize them under other
249 $info['type'] = 'other';
250 }
251 $extByType[$info['type']][$ext] = $info;
252 }
253
254 foreach ( $types as $type => $message ) {
255 if ( !isset( $extByType[$type] ) ) {
256 continue;
257 }
258 $extHtml .= Html::element( 'h2', [], $message );
259 foreach ( $extByType[$type] as $ext => $info ) {
260 $attribs = [
261 'data-name' => $ext,
262 'class' => 'config-ext-input cdx-checkbox__input'
263 ];
264 $labelAttribs = [];
265 if ( isset( $info['requires']['extensions'] ) ) {
266 $dependencyMap[$ext]['extensions'] = $info['requires']['extensions'];
267 $labelAttribs['class'] = 'mw-ext-with-dependencies';
268 }
269 if ( isset( $info['requires']['skins'] ) ) {
270 $dependencyMap[$ext]['skins'] = $info['requires']['skins'];
271 $labelAttribs['class'] = 'mw-ext-with-dependencies';
272 }
273 if ( isset( $dependencyMap[$ext] ) ) {
274 $links = [];
275 // For each dependency, link to the checkbox for each
276 // extension/skin that is required
277 if ( isset( $dependencyMap[$ext]['extensions'] ) ) {
278 foreach ( $dependencyMap[$ext]['extensions'] as $name ) {
279 $links[] = Html::element(
280 'a',
281 [ 'href' => "#config_ext-$name" ],
282 $name
283 );
284 }
285 }
286 if ( isset( $dependencyMap[$ext]['skins'] ) ) {
287 // @phan-suppress-next-line PhanTypeMismatchForeach Phan internal bug
288 foreach ( $dependencyMap[$ext]['skins'] as $name ) {
289 $links[] = Html::element(
290 'a',
291 [ 'href' => "#config_skin-$name" ],
292 $name
293 );
294 }
295 }
296
297 $text = wfMessage( 'config-extensions-requires' )
298 ->rawParams( $ext, $wgLang->commaList( $links ) )
299 ->escaped();
300 } else {
301 $text = $ext;
302 }
303 $extHtml .= $this->parent->getCheckBox( [
304 'var' => "ext-$ext",
305 'rawtext' => $text . $this->makeMoreInfoLink( $info ),
306 'attribs' => $attribs,
307 'labelAttribs' => $labelAttribs,
308 ] );
309 }
310 }
311
312 $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
313 $this->getFieldsetEnd();
314 $this->addHTML( $extHtml );
315 // Push the dependency map to the client side
316 $this->addHTML( Html::inlineScript(
317 'var extDependencyMap = ' . Html::encodeJsVar( $dependencyMap )
318 ) );
319 }
320 }
321
325 private function addFileOptions(): void {
326 // Having / in paths in Windows looks funny :)
327 $this->setVar( 'wgDeletedDirectory',
328 str_replace(
329 '/', DIRECTORY_SEPARATOR,
330 $this->getVar( 'wgDeletedDirectory' )
331 )
332 );
333
334 $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
335 $this->addHTML(
336 # Uploading
337 $this->getFieldsetStart( 'config-upload-settings' ) .
338 $this->parent->getCheckBox( [
339 'var' => 'wgEnableUploads',
340 'label' => 'config-upload-enable',
341 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ],
342 'help' => $this->parent->getHelpBox( 'config-upload-help' )
343 ] ) .
344 '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
345 $this->parent->getTextBox( [
346 'var' => 'wgDeletedDirectory',
347 'label' => 'config-upload-deleted',
348 'attribs' => [ 'dir' => 'ltr' ],
349 'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
350 ] ) .
351 '</div>'
352 );
353 $this->addHTML(
354 $this->parent->getCheckBox( [
355 'var' => 'wgUseInstantCommons',
356 'label' => 'config-instantcommons',
357 'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
358 ] ) .
359 $this->getFieldsetEnd()
360 );
361 }
362
366 private function addAdvancedOptions(): void {
367 $caches = [ 'none' ];
368 $cachevalDefault = 'none';
369
370 if ( count( $this->getVar( '_Caches' ) ) ) {
371 // A CACHE_ACCEL implementation is available
372 $caches[] = 'accel';
373 $cachevalDefault = 'accel';
374 }
375 $caches[] = 'memcached';
376
377 // We'll hide/show this on demand when the value changes, see config.js.
378 $cacheval = $this->getVar( '_MainCacheType' );
379 if ( !$cacheval ) {
380 // We need to set a default here; but don't hardcode it
381 // or we lose it every time we reload the page for validation
382 // or going back!
383 $cacheval = $cachevalDefault;
384 }
385 $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none';
386 $this->addHTML(
387 # Advanced settings
388 $this->getFieldsetStart( 'config-advanced-settings' ) .
389 # Object cache settings
390 // getRadioSet() builds a set of labeled radio buttons.
391 // For grep: The following messages are used as the item labels:
392 // config-cache-none, config-cache-accel, config-cache-memcached
393 $this->parent->getRadioSet( [
394 'var' => '_MainCacheType',
395 'label' => 'config-cache-options',
396 'itemLabelPrefix' => 'config-cache-',
397 'values' => $caches,
398 'value' => $cacheval,
399 ] ) .
400 $this->parent->getHelpBox( 'config-cache-help' ) .
401 "<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
402 $this->parent->getTextArea( [
403 'var' => '_MemCachedServers',
404 'label' => 'config-memcached-servers',
405 'help' => $this->parent->getHelpBox( 'config-memcached-help' )
406 ] ) .
407 '</div>' .
408 $this->getFieldsetEnd()
409 );
410 }
411
417 private function makeScreenshotsLink( $name, $screenshots ) {
418 global $wgLang;
419 if ( count( $screenshots ) > 1 ) {
420 $links = [];
421 $counter = 1;
422
423 foreach ( $screenshots as $shot ) {
424 $links[] = Html::element(
425 'a',
426 [ 'href' => $shot, 'target' => '_blank' ],
427 $wgLang->formatNum( $counter++ )
428 );
429 }
430 return wfMessage( 'config-skins-screenshots' )
431 ->rawParams( $name, $wgLang->commaList( $links ) )
432 ->escaped();
433 } else {
434 $link = Html::element(
435 'a',
436 [ 'href' => $screenshots[0], 'target' => '_blank' ],
437 wfMessage( 'config-screenshot' )->text()
438 );
439 return wfMessage( 'config-skins-screenshot', $name )->rawParams( $link )->escaped();
440 }
441 }
442
447 private function makeMoreInfoLink( $info ) {
448 if ( !isset( $info['url'] ) ) {
449 return '';
450 }
451 return ' ' . wfMessage( 'parentheses' )->rawParams(
452 Html::element(
453 'a',
454 [ 'href' => $info['url'] ],
455 wfMessage( 'config-ext-skins-more-info' )->text()
456 )
457 )->escaped();
458 }
459
466 public function submitSkins() {
467 $skins = array_keys( $this->parent->findExtensions( 'skins' )->value );
468 $this->parent->setVar( '_Skins', $skins );
469
470 if ( $skins ) {
471 $skinNames = array_map( 'strtolower', $skins );
472 $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
473 }
474
475 return true;
476 }
477
481 public function submit() {
482 $this->parent->setVarsFromRequest( [ '_RightsProfile', '_LicenseCode',
483 'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads',
484 '_Logo1x', '_LogoWordmark', '_LogoTagline', '_LogoIcon',
485 'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
486 'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers',
487 'wgUseInstantCommons', 'wgDefaultSkin' ] );
488
489 $retVal = true;
490
491 if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
492 $this->setVar( '_RightsProfile', array_key_first( $this->parent->rightsProfiles ) );
493 }
494
495 $code = $this->getVar( '_LicenseCode' );
496 if ( array_key_exists( $code, $this->parent->licenses ) ) {
497 // Messages:
498 // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
499 // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none
500 $entry = $this->parent->licenses[$code];
501 $this->setVar( 'wgRightsText',
502 $entry['text'] ?? wfMessage( 'config-license-' . $code )->text() );
503 $this->setVar( 'wgRightsUrl', $entry['url'] );
504 $this->setVar( 'wgRightsIcon', $entry['icon'] );
505 } else {
506 $this->setVar( 'wgRightsText', '' );
507 $this->setVar( 'wgRightsUrl', '' );
508 $this->setVar( 'wgRightsIcon', '' );
509 }
510
511 $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' )->value );
512 $skinsToInstall = [];
513 foreach ( $skinsAvailable as $skin ) {
514 $this->parent->setVarsFromRequest( [ "skin-$skin" ] );
515 if ( $this->getVar( "skin-$skin" ) ) {
516 $skinsToInstall[] = $skin;
517 }
518 }
519 $this->parent->setVar( '_Skins', $skinsToInstall );
520
521 if ( !$skinsToInstall && $skinsAvailable ) {
522 $this->parent->showError( 'config-skins-must-enable-some' );
523 $retVal = false;
524 }
525 $defaultSkin = $this->getVar( 'wgDefaultSkin' );
526 $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall );
527 if ( $skinsToInstall && !in_array( $defaultSkin, $skinsToInstallLowercase ) ) {
528 $this->parent->showError( 'config-skins-must-enable-default' );
529 $retVal = false;
530 }
531
532 $extsAvailable = array_keys( $this->parent->findExtensions()->value );
533 $extsToInstall = [];
534 foreach ( $extsAvailable as $ext ) {
535 $this->parent->setVarsFromRequest( [ "ext-$ext" ] );
536 if ( $this->getVar( "ext-$ext" ) ) {
537 $extsToInstall[] = $ext;
538 }
539 }
540 $this->parent->setVar( '_Extensions', $extsToInstall );
541
542 if ( $this->getVar( '_MainCacheType' ) == 'memcached' ) {
543 $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
544 // FIXME: explode() will always result in an array of at least one string, even on null (when
545 // the string will be empty and you'll get a PHP warning), so this has never worked?
546 // @phan-suppress-next-line PhanImpossibleCondition
547 if ( !$memcServers ) {
548 $this->parent->showError( 'config-memcache-needservers' );
549 $retVal = false;
550 }
551
552 foreach ( $memcServers as $server ) {
553 $memcParts = explode( ":", $server, 2 );
554 if ( !isset( $memcParts[0] )
555 || ( !IPUtils::isValid( $memcParts[0] )
556 && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
557 ) {
558 $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
559 $retVal = false;
560 } elseif ( !isset( $memcParts[1] ) ) {
561 $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
562 $retVal = false;
563 } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
564 $this->parent->showError( 'config-memcache-badport', 1, 65535 );
565 $retVal = false;
566 }
567 }
568 }
569
570 return $retVal;
571 }
572
573}
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:562
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
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.