WebInstallerOptions.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. <?php
  2. /**
  3. * This program is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along
  14. * with this program; if not, write to the Free Software Foundation, Inc.,
  15. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * http://www.gnu.org/copyleft/gpl.html
  17. *
  18. * @file
  19. * @ingroup Deployment
  20. */
  21. class WebInstallerOptions extends WebInstallerPage {
  22. /**
  23. * @return string|null
  24. */
  25. public function execute() {
  26. global $wgLang;
  27. if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
  28. $this->submitSkins();
  29. return 'skip';
  30. }
  31. if ( $this->parent->request->wasPosted() && $this->submit() ) {
  32. return 'continue';
  33. }
  34. $emailwrapperStyle = $this->getVar( 'wgEnableEmail' ) ? '' : 'display: none';
  35. $this->startForm();
  36. $this->addHTML(
  37. # User Rights
  38. // getRadioSet() builds a set of labeled radio buttons.
  39. // For grep: The following messages are used as the item labels:
  40. // config-profile-wiki, config-profile-no-anon, config-profile-fishbowl, config-profile-private
  41. $this->parent->getRadioSet( [
  42. 'var' => '_RightsProfile',
  43. 'label' => 'config-profile',
  44. 'itemLabelPrefix' => 'config-profile-',
  45. 'values' => array_keys( $this->parent->rightsProfiles ),
  46. ] ) .
  47. $this->parent->getInfoBox( wfMessage( 'config-profile-help' )->plain() ) .
  48. # Licensing
  49. // getRadioSet() builds a set of labeled radio buttons.
  50. // For grep: The following messages are used as the item labels:
  51. // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
  52. // config-license-cc-0, config-license-pd, config-license-gfdl,
  53. // config-license-none, config-license-cc-choose
  54. $this->parent->getRadioSet( [
  55. 'var' => '_LicenseCode',
  56. 'label' => 'config-license',
  57. 'itemLabelPrefix' => 'config-license-',
  58. 'values' => array_keys( $this->parent->licenses ),
  59. 'commonAttribs' => [ 'class' => 'licenseRadio' ],
  60. ] ) .
  61. $this->getCCChooser() .
  62. $this->parent->getHelpBox( 'config-license-help' ) .
  63. # E-mail
  64. $this->getFieldsetStart( 'config-email-settings' ) .
  65. $this->parent->getCheckBox( [
  66. 'var' => 'wgEnableEmail',
  67. 'label' => 'config-enable-email',
  68. 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ],
  69. ] ) .
  70. $this->parent->getHelpBox( 'config-enable-email-help' ) .
  71. "<div id=\"emailwrapper\" style=\"$emailwrapperStyle\">" .
  72. $this->parent->getTextBox( [
  73. 'var' => 'wgPasswordSender',
  74. 'label' => 'config-email-sender'
  75. ] ) .
  76. $this->parent->getHelpBox( 'config-email-sender-help' ) .
  77. $this->parent->getCheckBox( [
  78. 'var' => 'wgEnableUserEmail',
  79. 'label' => 'config-email-user',
  80. ] ) .
  81. $this->parent->getHelpBox( 'config-email-user-help' ) .
  82. $this->parent->getCheckBox( [
  83. 'var' => 'wgEnotifUserTalk',
  84. 'label' => 'config-email-usertalk',
  85. ] ) .
  86. $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
  87. $this->parent->getCheckBox( [
  88. 'var' => 'wgEnotifWatchlist',
  89. 'label' => 'config-email-watchlist',
  90. ] ) .
  91. $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
  92. $this->parent->getCheckBox( [
  93. 'var' => 'wgEmailAuthentication',
  94. 'label' => 'config-email-auth',
  95. ] ) .
  96. $this->parent->getHelpBox( 'config-email-auth-help' ) .
  97. "</div>" .
  98. $this->getFieldsetEnd()
  99. );
  100. $skins = $this->parent->findExtensions( 'skins' )->value;
  101. '@phan-var array[] $skins';
  102. $skinHtml = $this->getFieldsetStart( 'config-skins' );
  103. $skinNames = array_map( 'strtolower', array_keys( $skins ) );
  104. $chosenSkinName = $this->getVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
  105. if ( $skins ) {
  106. $radioButtons = $this->parent->getRadioElements( [
  107. 'var' => 'wgDefaultSkin',
  108. 'itemLabels' => array_fill_keys( $skinNames, 'config-skins-use-as-default' ),
  109. 'values' => $skinNames,
  110. 'value' => $chosenSkinName,
  111. ] );
  112. foreach ( $skins as $skin => $info ) {
  113. if ( isset( $info['screenshots'] ) ) {
  114. $screenshotText = $this->makeScreenshotsLink( $skin, $info['screenshots'] );
  115. } else {
  116. $screenshotText = htmlspecialchars( $skin );
  117. }
  118. $skinHtml .=
  119. '<div class="config-skins-item">' .
  120. $this->parent->getCheckBox( [
  121. 'var' => "skin-$skin",
  122. 'rawtext' => $screenshotText,
  123. 'value' => $this->getVar( "skin-$skin", true ), // all found skins enabled by default
  124. ] ) .
  125. '<div class="config-skins-use-as-default">' . $radioButtons[strtolower( $skin )] . '</div>' .
  126. '</div>';
  127. }
  128. } else {
  129. $skinHtml .=
  130. Html::warningBox( wfMessage( 'config-skins-missing' )->plain(), 'config-warning-box' ) .
  131. Html::hidden( 'config_wgDefaultSkin', $chosenSkinName );
  132. }
  133. $skinHtml .= $this->parent->getHelpBox( 'config-skins-help' ) .
  134. $this->getFieldsetEnd();
  135. $this->addHTML( $skinHtml );
  136. $extensions = $this->parent->findExtensions()->value;
  137. '@phan-var array[] $extensions';
  138. $dependencyMap = [];
  139. if ( $extensions ) {
  140. $extHtml = $this->getFieldsetStart( 'config-extensions' );
  141. $extByType = [];
  142. $types = SpecialVersion::getExtensionTypes();
  143. // Sort by type first
  144. foreach ( $extensions as $ext => $info ) {
  145. if ( !isset( $info['type'] ) || !isset( $types[$info['type']] ) ) {
  146. // We let extensions normally define custom types, but
  147. // since we aren't loading extensions, we'll have to
  148. // categorize them under other
  149. $info['type'] = 'other';
  150. }
  151. $extByType[$info['type']][$ext] = $info;
  152. }
  153. foreach ( $types as $type => $message ) {
  154. if ( !isset( $extByType[$type] ) ) {
  155. continue;
  156. }
  157. $extHtml .= Html::element( 'h2', [], $message );
  158. foreach ( $extByType[$type] as $ext => $info ) {
  159. $urlText = '';
  160. if ( isset( $info['url'] ) ) {
  161. $urlText = ' ' . Html::element( 'a', [ 'href' => $info['url'] ], '(more information)' );
  162. }
  163. $attribs = [
  164. 'data-name' => $ext,
  165. 'class' => 'config-ext-input'
  166. ];
  167. $labelAttribs = [];
  168. $fullDepList = [];
  169. if ( isset( $info['requires']['extensions'] ) ) {
  170. $dependencyMap[$ext]['extensions'] = $info['requires']['extensions'];
  171. $labelAttribs['class'] = 'mw-ext-with-dependencies';
  172. }
  173. if ( isset( $info['requires']['skins'] ) ) {
  174. $dependencyMap[$ext]['skins'] = $info['requires']['skins'];
  175. $labelAttribs['class'] = 'mw-ext-with-dependencies';
  176. }
  177. if ( isset( $dependencyMap[$ext] ) ) {
  178. $links = [];
  179. // For each dependency, link to the checkbox for each
  180. // extension/skin that is required
  181. if ( isset( $dependencyMap[$ext]['extensions'] ) ) {
  182. foreach ( $dependencyMap[$ext]['extensions'] as $name ) {
  183. $links[] = Html::element(
  184. 'a',
  185. [ 'href' => "#config_ext-$name" ],
  186. $name
  187. );
  188. }
  189. }
  190. if ( isset( $dependencyMap[$ext]['skins'] ) ) {
  191. foreach ( $dependencyMap[$ext]['skins'] as $name ) {
  192. $links[] = Html::element(
  193. 'a',
  194. [ 'href' => "#config_skin-$name" ],
  195. $name
  196. );
  197. }
  198. }
  199. $text = wfMessage( 'config-extensions-requires' )
  200. ->rawParams( $ext, $wgLang->commaList( $links ) )
  201. ->escaped();
  202. } else {
  203. $text = $ext;
  204. }
  205. $extHtml .= $this->parent->getCheckBox( [
  206. 'var' => "ext-$ext",
  207. 'rawtext' => $text,
  208. 'attribs' => $attribs,
  209. 'labelAttribs' => $labelAttribs,
  210. ] );
  211. }
  212. }
  213. $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
  214. $this->getFieldsetEnd();
  215. $this->addHTML( $extHtml );
  216. // Push the dependency map to the client side
  217. $this->addHTML( Html::inlineScript(
  218. 'var extDependencyMap = ' . Xml::encodeJsVar( $dependencyMap )
  219. ) );
  220. }
  221. // Having / in paths in Windows looks funny :)
  222. $this->setVar( 'wgDeletedDirectory',
  223. str_replace(
  224. '/', DIRECTORY_SEPARATOR,
  225. $this->getVar( 'wgDeletedDirectory' )
  226. )
  227. );
  228. $uploadwrapperStyle = $this->getVar( 'wgEnableUploads' ) ? '' : 'display: none';
  229. $this->addHTML(
  230. # Uploading
  231. $this->getFieldsetStart( 'config-upload-settings' ) .
  232. $this->parent->getCheckBox( [
  233. 'var' => 'wgEnableUploads',
  234. 'label' => 'config-upload-enable',
  235. 'attribs' => [ 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ],
  236. 'help' => $this->parent->getHelpBox( 'config-upload-help' )
  237. ] ) .
  238. '<div id="uploadwrapper" style="' . $uploadwrapperStyle . '">' .
  239. $this->parent->getTextBox( [
  240. 'var' => 'wgDeletedDirectory',
  241. 'label' => 'config-upload-deleted',
  242. 'attribs' => [ 'dir' => 'ltr' ],
  243. 'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
  244. ] ) .
  245. '</div>' .
  246. $this->parent->getTextBox( [
  247. 'var' => 'wgLogo',
  248. 'label' => 'config-logo',
  249. 'attribs' => [ 'dir' => 'ltr' ],
  250. 'help' => $this->parent->getHelpBox( 'config-logo-help' )
  251. ] )
  252. );
  253. $this->addHTML(
  254. $this->parent->getCheckBox( [
  255. 'var' => 'wgUseInstantCommons',
  256. 'label' => 'config-instantcommons',
  257. 'help' => $this->parent->getHelpBox( 'config-instantcommons-help' )
  258. ] ) .
  259. $this->getFieldsetEnd()
  260. );
  261. $caches = [ 'none' ];
  262. $cachevalDefault = 'none';
  263. if ( count( $this->getVar( '_Caches' ) ) ) {
  264. // A CACHE_ACCEL implementation is available
  265. $caches[] = 'accel';
  266. $cachevalDefault = 'accel';
  267. }
  268. $caches[] = 'memcached';
  269. // We'll hide/show this on demand when the value changes, see config.js.
  270. $cacheval = $this->getVar( '_MainCacheType' );
  271. if ( !$cacheval ) {
  272. // We need to set a default here; but don't hardcode it
  273. // or we lose it every time we reload the page for validation
  274. // or going back!
  275. $cacheval = $cachevalDefault;
  276. }
  277. $hidden = ( $cacheval == 'memcached' ) ? '' : 'display: none';
  278. $this->addHTML(
  279. # Advanced settings
  280. $this->getFieldsetStart( 'config-advanced-settings' ) .
  281. # Object cache settings
  282. // getRadioSet() builds a set of labeled radio buttons.
  283. // For grep: The following messages are used as the item labels:
  284. // config-cache-none, config-cache-accel, config-cache-memcached
  285. $this->parent->getRadioSet( [
  286. 'var' => '_MainCacheType',
  287. 'label' => 'config-cache-options',
  288. 'itemLabelPrefix' => 'config-cache-',
  289. 'values' => $caches,
  290. 'value' => $cacheval,
  291. ] ) .
  292. $this->parent->getHelpBox( 'config-cache-help' ) .
  293. "<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
  294. $this->parent->getTextArea( [
  295. 'var' => '_MemCachedServers',
  296. 'label' => 'config-memcached-servers',
  297. 'help' => $this->parent->getHelpBox( 'config-memcached-help' )
  298. ] ) .
  299. '</div>' .
  300. $this->getFieldsetEnd()
  301. );
  302. $this->endForm();
  303. return null;
  304. }
  305. /**
  306. * @param string $name
  307. * @param array $screenshots
  308. */
  309. private function makeScreenshotsLink( $name, $screenshots ) {
  310. global $wgLang;
  311. if ( count( $screenshots ) > 1 ) {
  312. $links = [];
  313. $counter = 1;
  314. foreach ( $screenshots as $shot ) {
  315. $links[] = Html::element(
  316. 'a',
  317. [ 'href' => $shot, 'target' => '_blank' ],
  318. $wgLang->formatNum( $counter++ )
  319. );
  320. }
  321. return wfMessage( 'config-skins-screenshots' )
  322. ->rawParams( $name, $wgLang->commaList( $links ) )
  323. ->escaped();
  324. } else {
  325. $link = Html::element(
  326. 'a',
  327. [ 'href' => $screenshots[0], 'target' => '_blank' ],
  328. wfMessage( 'config-screenshot' )->text()
  329. );
  330. return wfMessage( 'config-skins-screenshot', $name )->rawParams( $link )->escaped();
  331. }
  332. }
  333. /**
  334. * @return string
  335. */
  336. public function getCCPartnerUrl() {
  337. $server = $this->getVar( 'wgServer' );
  338. $exitUrl = $server . $this->parent->getUrl( [
  339. 'page' => 'Options',
  340. 'SubmitCC' => 'indeed',
  341. 'config__LicenseCode' => 'cc',
  342. 'config_wgRightsUrl' => '[license_url]',
  343. 'config_wgRightsText' => '[license_name]',
  344. 'config_wgRightsIcon' => '[license_button]',
  345. ] );
  346. $styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
  347. '/mw-config/config-cc.css';
  348. $iframeUrl = 'https://creativecommons.org/license/?' .
  349. wfArrayToCgi( [
  350. 'partner' => 'MediaWiki',
  351. 'exit_url' => $exitUrl,
  352. 'lang' => $this->getVar( '_UserLang' ),
  353. 'stylesheet' => $styleUrl,
  354. ] );
  355. return $iframeUrl;
  356. }
  357. /**
  358. * @return string
  359. */
  360. public function getCCChooser() {
  361. $iframeAttribs = [
  362. 'class' => 'config-cc-iframe',
  363. 'name' => 'config-cc-iframe',
  364. 'id' => 'config-cc-iframe',
  365. 'frameborder' => 0,
  366. 'width' => '100%',
  367. 'height' => '100%',
  368. ];
  369. if ( $this->getVar( '_CCDone' ) ) {
  370. $iframeAttribs['src'] = $this->parent->getUrl( [ 'ShowCC' => 'yes' ] );
  371. } else {
  372. $iframeAttribs['src'] = $this->getCCPartnerUrl();
  373. }
  374. $wrapperStyle = ( $this->getVar( '_LicenseCode' ) == 'cc-choose' ) ? '' : 'display: none';
  375. return "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"$wrapperStyle\">\n" .
  376. Html::element( 'iframe', $iframeAttribs ) .
  377. "</div>\n";
  378. }
  379. /**
  380. * @return string
  381. */
  382. public function getCCDoneBox() {
  383. $js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
  384. // If you change this height, also change it in config.css
  385. $expandJs = str_replace( '$1', '54em', $js );
  386. $reduceJs = str_replace( '$1', '70px', $js );
  387. return '<p>' .
  388. Html::element( 'img', [ 'src' => $this->getVar( 'wgRightsIcon' ) ] ) .
  389. "\u{00A0}\u{00A0}" .
  390. htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
  391. "</p>\n" .
  392. "<p style=\"text-align: center;\">" .
  393. Html::element( 'a',
  394. [
  395. 'href' => $this->getCCPartnerUrl(),
  396. 'onclick' => $expandJs,
  397. ],
  398. wfMessage( 'config-cc-again' )->text()
  399. ) .
  400. "</p>\n" .
  401. "<script>\n" .
  402. # Reduce the wrapper div height
  403. htmlspecialchars( $reduceJs ) .
  404. "\n" .
  405. "</script>\n";
  406. }
  407. public function submitCC() {
  408. $newValues = $this->parent->setVarsFromRequest(
  409. [ 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ] );
  410. if ( count( $newValues ) != 3 ) {
  411. $this->parent->showError( 'config-cc-error' );
  412. return;
  413. }
  414. $this->setVar( '_CCDone', true );
  415. $this->addHTML( $this->getCCDoneBox() );
  416. }
  417. /**
  418. * If the user skips this installer page, we still need to set up the default skins, but ignore
  419. * everything else.
  420. *
  421. * @return bool
  422. */
  423. public function submitSkins() {
  424. $skins = array_keys( $this->parent->findExtensions( 'skins' )->value );
  425. $this->parent->setVar( '_Skins', $skins );
  426. if ( $skins ) {
  427. $skinNames = array_map( 'strtolower', $skins );
  428. $this->parent->setVar( 'wgDefaultSkin', $this->parent->getDefaultSkin( $skinNames ) );
  429. }
  430. return true;
  431. }
  432. /**
  433. * @return bool
  434. */
  435. public function submit() {
  436. $this->parent->setVarsFromRequest( [ '_RightsProfile', '_LicenseCode',
  437. 'wgEnableEmail', 'wgPasswordSender', 'wgEnableUploads', 'wgLogo',
  438. 'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
  439. 'wgEmailAuthentication', '_MainCacheType', '_MemCachedServers',
  440. 'wgUseInstantCommons', 'wgDefaultSkin' ] );
  441. $retVal = true;
  442. if ( !array_key_exists( $this->getVar( '_RightsProfile' ), $this->parent->rightsProfiles ) ) {
  443. reset( $this->parent->rightsProfiles );
  444. $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
  445. }
  446. $code = $this->getVar( '_LicenseCode' );
  447. if ( $code == 'cc-choose' ) {
  448. if ( !$this->getVar( '_CCDone' ) ) {
  449. $this->parent->showError( 'config-cc-not-chosen' );
  450. $retVal = false;
  451. }
  452. } elseif ( array_key_exists( $code, $this->parent->licenses ) ) {
  453. // Messages:
  454. // config-license-cc-by, config-license-cc-by-sa, config-license-cc-by-nc-sa,
  455. // config-license-cc-0, config-license-pd, config-license-gfdl, config-license-none,
  456. // config-license-cc-choose
  457. $entry = $this->parent->licenses[$code];
  458. $this->setVar( 'wgRightsText',
  459. $entry['text'] ?? wfMessage( 'config-license-' . $code )->text() );
  460. $this->setVar( 'wgRightsUrl', $entry['url'] );
  461. $this->setVar( 'wgRightsIcon', $entry['icon'] );
  462. } else {
  463. $this->setVar( 'wgRightsText', '' );
  464. $this->setVar( 'wgRightsUrl', '' );
  465. $this->setVar( 'wgRightsIcon', '' );
  466. }
  467. $skinsAvailable = array_keys( $this->parent->findExtensions( 'skins' )->value );
  468. $skinsToInstall = [];
  469. foreach ( $skinsAvailable as $skin ) {
  470. $this->parent->setVarsFromRequest( [ "skin-$skin" ] );
  471. if ( $this->getVar( "skin-$skin" ) ) {
  472. $skinsToInstall[] = $skin;
  473. }
  474. }
  475. $this->parent->setVar( '_Skins', $skinsToInstall );
  476. if ( !$skinsToInstall && $skinsAvailable ) {
  477. $this->parent->showError( 'config-skins-must-enable-some' );
  478. $retVal = false;
  479. }
  480. $defaultSkin = $this->getVar( 'wgDefaultSkin' );
  481. $skinsToInstallLowercase = array_map( 'strtolower', $skinsToInstall );
  482. if ( $skinsToInstall && array_search( $defaultSkin, $skinsToInstallLowercase ) === false ) {
  483. $this->parent->showError( 'config-skins-must-enable-default' );
  484. $retVal = false;
  485. }
  486. $extsAvailable = array_keys( $this->parent->findExtensions()->value );
  487. $extsToInstall = [];
  488. foreach ( $extsAvailable as $ext ) {
  489. $this->parent->setVarsFromRequest( [ "ext-$ext" ] );
  490. if ( $this->getVar( "ext-$ext" ) ) {
  491. $extsToInstall[] = $ext;
  492. }
  493. }
  494. $this->parent->setVar( '_Extensions', $extsToInstall );
  495. if ( $this->getVar( '_MainCacheType' ) == 'memcached' ) {
  496. $memcServers = explode( "\n", $this->getVar( '_MemCachedServers' ) );
  497. if ( !$memcServers ) {
  498. $this->parent->showError( 'config-memcache-needservers' );
  499. $retVal = false;
  500. }
  501. foreach ( $memcServers as $server ) {
  502. $memcParts = explode( ":", $server, 2 );
  503. if ( !isset( $memcParts[0] )
  504. || ( !IP::isValid( $memcParts[0] )
  505. && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) )
  506. ) {
  507. $this->parent->showError( 'config-memcache-badip', $memcParts[0] );
  508. $retVal = false;
  509. } elseif ( !isset( $memcParts[1] ) ) {
  510. $this->parent->showError( 'config-memcache-noport', $memcParts[0] );
  511. $retVal = false;
  512. } elseif ( $memcParts[1] < 1 || $memcParts[1] > 65535 ) {
  513. $this->parent->showError( 'config-memcache-badport', 1, 65535 );
  514. $retVal = false;
  515. }
  516. }
  517. }
  518. return $retVal;
  519. }
  520. }