From: jenkins-bot Date: Tue, 21 Nov 2017 15:21:26 +0000 (+0000) Subject: Merge "Add string length limits" X-Git-Tag: 1.31.0-rc.0~1445 X-Git-Url: http://git.cyclocoop.org/%22%2C%20generer_url_ecrire%28?a=commitdiff_plain;h=9f3bc1fe5978c24230d0b6a92a83560f9d638a18;hp=17d001b73a3bd291e0f77794fa60fa5495609d68;p=lhc%2Fweb%2Fwiklou.git Merge "Add string length limits" --- diff --git a/.mailmap b/.mailmap index 5a76fb950e..c4f86040f3 100644 --- a/.mailmap +++ b/.mailmap @@ -413,8 +413,9 @@ Sumit Asthana TerraCodes Thalia Chan Thalia Chan -Thiemo Mättig -Thiemo Mättig +Thiemo Kreuz +Thiemo Kreuz +Thiemo Kreuz This, that and the other tholam Thomas Bleher diff --git a/CREDITS b/CREDITS index c38c3fcc05..6ab4ad3e14 100644 --- a/CREDITS +++ b/CREDITS @@ -625,7 +625,7 @@ The following list can be found parsed under Special:Version/Credits --> * The Discoverer * The Evil IP address * theopolisme -* Thiemo Mättig +* Thiemo Kreuz * This, that and the other * tholam * Thomas Arrow diff --git a/RELEASE-NOTES-1.30 b/RELEASE-NOTES-1.30 index f79ae838a8..1449dab47e 100644 --- a/RELEASE-NOTES-1.30 +++ b/RELEASE-NOTES-1.30 @@ -80,12 +80,19 @@ section). === External library changes in 1.30 === ==== Upgraded external libraries ==== -* mediawiki/mediawiki-codesniffer updated to 0.8.1. -* wikimedia/composer-merge-plugin updated to 1.4.1. +* Updated justinrainbow/json-schema from v3.0 to v5.2. +* Updated mediawiki/mediawiki-codesniffer from v0.7.2 to v0.12.0. +* Updated wikimedia/composer-merge-plugin from v1.4.0 to v1.4.1. +* Updated wikimedia/relpath from v1.0.3 to v2.0.0. +* Updated OOjs from v2.0.0 to v2.1.0. +* Updated OOUI from v0.21.1 to v0.23.0. +* Updated QUnit from v1.23.1 to v2.4.0. +* Updated phpunit/phpunit from v4.8.35 to v4.8.36. ==== New external libraries ==== * The class \TestingAccessWrapper has been moved to the external library wikimedia/testing-access-wrapper and renamed \Wikimedia\TestingAccessWrapper. +* Purtle, a fast, lightweight RDF generator. ==== Removed and replaced external libraries ==== * … diff --git a/RELEASE-NOTES-1.31 b/RELEASE-NOTES-1.31 index 9a4c74ce18..f1fd9d3ac3 100644 --- a/RELEASE-NOTES-1.31 +++ b/RELEASE-NOTES-1.31 @@ -15,11 +15,13 @@ production. possible for fallback images such as png. * (T44246) $wgFilterLogTypes will no longer ignore 'patrol' when user does not have the right to mark things patrolled. -* … === New features in 1.31 === * Wikimedia\Rdbms\IDatabase->select() and similar methods now support joins with parentheses for grouping. +* As a first pass in standardizing dialog boxes across the MediaWiki product, + Html class now provides helper methods for messageBox, successBox, errorBox and + warningBox generation. === External library changes in 1.31 === @@ -38,11 +40,9 @@ production. 'mediawiki.viewport' module instead. * The deprecated 'mediawiki.widgets.CategorySelector' module alias was removed. Use the 'mediawiki.widgets.CategoryMultiselectWidget' module directly instead. -* … === Bug fixes in 1.31 === * (T90902) Non-breaking space in header ID breaks anchor -* … === Action API changes in 1.31 === * … @@ -55,7 +55,7 @@ MediaWiki supports over 350 languages. Many localisations are updated regularly. Below only new and removed languages are listed, as well as changes to languages because of Phabricator reports. -* … +* (T180052) Mirandese (mwl) now supports gendered NS_USER/NS_USER_TALK namespaces. === Other changes in 1.31 === * MessageBlobStore::insertMessageBlob() (deprecated in 1.27) was removed. @@ -93,9 +93,15 @@ changes to languages because of Phabricator reports. * Revision::selectArchiveFields() → Revision::getArchiveQueryInfo() * User::selectFields() → User::getQueryInfo() * WikiPage::selectFields() → WikiPage::getQueryInfo() - * Due to significant refactoring, method ContribsPager::getUserCond() that had - no access restriction has been removed. - * Revision::setUserIdAndName() was deprecated. +* Due to significant refactoring, method ContribsPager::getUserCond() that had + no access restriction has been removed. +* Revision::setUserIdAndName() was deprecated. +* Access to TitleValue class properties was deprecated, the relevant getters + should be used instead. +* DifferenceEngine::getDiffBodyCacheKey() is deprecated. Subclasses should + override DifferenceEngine::getDiffBodyCacheKeyParams() instead. +* The deprecated MW_DIFF_VERSION constant was removed. + DifferenceEngine::MW_DIFF_VERSION should be used instead. == Compatibility == MediaWiki 1.31 requires PHP 5.5.9 or later. There is experimental support for diff --git a/api.php b/api.php index a6ce3b25e3..d9a69db37e 100644 --- a/api.php +++ b/api.php @@ -44,6 +44,17 @@ if ( !$wgRequest->checkUrlExtension() ) { return; } +// Pathinfo can be used for stupid things. We don't support it for api.php at +// all, so error out if it's present. +if ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) { + $correctUrl = wfAppendQuery( wfScript( 'api' ), $wgRequest->getQueryValues() ); + $correctUrl = wfExpandUrl( $correctUrl, PROTO_CANONICAL ); + header( "Location: $correctUrl", true, 301 ); + echo 'This endpoint does not support "path info", i.e. extra text between "api.php"' + . 'and the "?". Remove any such text and try again.'; + die( 1 ); +} + // Verify that the API has not been disabled if ( !$wgEnableAPI ) { header( $_SERVER['SERVER_PROTOCOL'] . ' 500 MediaWiki configuration Error', true, 500 ); diff --git a/autoload.php b/autoload.php index 39ec4b03b9..5a2156ac92 100644 --- a/autoload.php +++ b/autoload.php @@ -284,6 +284,7 @@ $wgAutoloadLocalClasses = [ 'ComposerJson' => __DIR__ . '/includes/libs/composer/ComposerJson.php', 'ComposerLock' => __DIR__ . '/includes/libs/composer/ComposerLock.php', 'ComposerPackageModifier' => __DIR__ . '/includes/composer/ComposerPackageModifier.php', + 'ComposerVendorHtaccessCreator' => __DIR__ . '/includes/composer/ComposerVendorHtaccessCreator.php', 'ComposerVersionNormalizer' => __DIR__ . '/includes/composer/ComposerVersionNormalizer.php', 'CompressOld' => __DIR__ . '/maintenance/storage/compressOld.php', 'ConcatenatedGzipHistoryBlob' => __DIR__ . '/includes/HistoryBlob.php', @@ -311,6 +312,7 @@ $wgAutoloadLocalClasses = [ 'CreateAndPromote' => __DIR__ . '/maintenance/createAndPromote.php', 'CreateFileOp' => __DIR__ . '/includes/libs/filebackend/fileop/CreateFileOp.php', 'CreditsAction' => __DIR__ . '/includes/actions/CreditsAction.php', + 'CrhConverter' => __DIR__ . '/languages/classes/LanguageCrh.php', 'CryptHKDF' => __DIR__ . '/includes/libs/CryptHKDF.php', 'CryptRand' => __DIR__ . '/includes/libs/CryptRand.php', 'CssContent' => __DIR__ . '/includes/content/CssContent.php', @@ -454,6 +456,7 @@ $wgAutoloadLocalClasses = [ 'ExtensionRegistry' => __DIR__ . '/includes/registration/ExtensionRegistry.php', 'ExternalStore' => __DIR__ . '/includes/externalstore/ExternalStore.php', 'ExternalStoreDB' => __DIR__ . '/includes/externalstore/ExternalStoreDB.php', + 'ExternalStoreFactory' => __DIR__ . '/includes/externalstore/ExternalStoreFactory.php', 'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php', 'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php', 'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php', @@ -704,6 +707,7 @@ $wgAutoloadLocalClasses = [ 'LanguageBs' => __DIR__ . '/languages/classes/LanguageBs.php', 'LanguageCode' => __DIR__ . '/languages/LanguageCode.php', 'LanguageConverter' => __DIR__ . '/languages/LanguageConverter.php', + 'LanguageCrh' => __DIR__ . '/languages/classes/LanguageCrh.php', 'LanguageCu' => __DIR__ . '/languages/classes/LanguageCu.php', 'LanguageDsb' => __DIR__ . '/languages/classes/LanguageDsb.php', 'LanguageEn' => __DIR__ . '/languages/classes/LanguageEn.php', @@ -883,6 +887,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Interwiki\\ClassicInterwikiLookup' => __DIR__ . '/includes/interwiki/ClassicInterwikiLookup.php', 'MediaWiki\\Interwiki\\InterwikiLookup' => __DIR__ . '/includes/interwiki/InterwikiLookup.php', 'MediaWiki\\Interwiki\\InterwikiLookupAdapter' => __DIR__ . '/includes/interwiki/InterwikiLookupAdapter.php', + 'MediaWiki\\Languages\\Data\\CrhExceptions' => __DIR__ . '/languages/data/CrhExceptions.php', 'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php', 'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php', 'MediaWiki\\Linker\\LinkRenderer' => __DIR__ . '/includes/linker/LinkRenderer.php', @@ -955,6 +960,7 @@ $wgAutoloadLocalClasses = [ 'MediaWiki\\Tidy\\RemexDriver' => __DIR__ . '/includes/tidy/RemexDriver.php', 'MediaWiki\\Tidy\\RemexMungerData' => __DIR__ . '/includes/tidy/RemexMungerData.php', 'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php', + 'MediaWiki\\User\\UserIdentity' => __DIR__ . '/includes/user/UserIdentity.php', 'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php', 'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php', 'MediaWiki\\Widget\\DateInputWidget' => __DIR__ . '/includes/widget/DateInputWidget.php', @@ -1217,6 +1223,7 @@ $wgAutoloadLocalClasses = [ 'RefreshLinks' => __DIR__ . '/maintenance/refreshLinks.php', 'RefreshLinksJob' => __DIR__ . '/includes/jobqueue/jobs/RefreshLinksJob.php', 'RegexlikeReplacer' => __DIR__ . '/includes/libs/replacers/RegexlikeReplacer.php', + 'RemexStripTagHandler' => __DIR__ . '/includes/parser/RemexStripTagHandler.php', 'RemoveInvalidEmails' => __DIR__ . '/maintenance/removeInvalidEmails.php', 'RemoveUnusedAccounts' => __DIR__ . '/maintenance/removeUnusedAccounts.php', 'RenameDbPrefix' => __DIR__ . '/maintenance/renameDbPrefix.php', @@ -1295,7 +1302,7 @@ $wgAutoloadLocalClasses = [ 'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php', 'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php', 'SamplingStatsdClient' => __DIR__ . '/includes/libs/stats/SamplingStatsdClient.php', - 'Sanitizer' => __DIR__ . '/includes/Sanitizer.php', + 'Sanitizer' => __DIR__ . '/includes/parser/Sanitizer.php', 'ScopedCallback' => __DIR__ . '/includes/compat/ScopedCallback.php', 'ScopedLock' => __DIR__ . '/includes/libs/lockmanager/ScopedLock.php', 'SearchApi' => __DIR__ . '/includes/api/SearchApi.php', @@ -1568,7 +1575,7 @@ $wgAutoloadLocalClasses = [ 'UserMailer' => __DIR__ . '/includes/mail/UserMailer.php', 'UserNamePrefixSearch' => __DIR__ . '/includes/user/UserNamePrefixSearch.php', 'UserNotLoggedIn' => __DIR__ . '/includes/exception/UserNotLoggedIn.php', - 'UserOptions' => __DIR__ . '/maintenance/userOptions.inc', + 'UserOptionsMaintenance' => __DIR__ . '/maintenance/userOptions.php', 'UserPasswordPolicy' => __DIR__ . '/includes/password/UserPasswordPolicy.php', 'UserRightsProxy' => __DIR__ . '/includes/user/UserRightsProxy.php', 'UserrightsPage' => __DIR__ . '/includes/specials/SpecialUserrights.php', @@ -1591,10 +1598,11 @@ $wgAutoloadLocalClasses = [ 'WantedQueryPage' => __DIR__ . '/includes/specialpage/WantedQueryPage.php', 'WantedTemplatesPage' => __DIR__ . '/includes/specials/SpecialWantedtemplates.php', 'WatchAction' => __DIR__ . '/includes/actions/WatchAction.php', - 'WatchedItem' => __DIR__ . '/includes/WatchedItem.php', - 'WatchedItemQueryService' => __DIR__ . '/includes/WatchedItemQueryService.php', - 'WatchedItemQueryServiceExtension' => __DIR__ . '/includes/WatchedItemQueryServiceExtension.php', - 'WatchedItemStore' => __DIR__ . '/includes/WatchedItemStore.php', + 'WatchedItem' => __DIR__ . '/includes/watcheditem/WatchedItem.php', + 'WatchedItemQueryService' => __DIR__ . '/includes/watcheditem/WatchedItemQueryService.php', + 'WatchedItemQueryServiceExtension' => __DIR__ . '/includes/watcheditem/WatchedItemQueryServiceExtension.php', + 'WatchedItemStore' => __DIR__ . '/includes/watcheditem/WatchedItemStore.php', + 'WatchedItemStoreInterface' => __DIR__ . '/includes/watcheditem/WatchedItemStoreInterface.php', 'WatchlistCleanup' => __DIR__ . '/maintenance/cleanupWatchlist.php', 'WebInstaller' => __DIR__ . '/includes/installer/WebInstaller.php', 'WebInstallerComplete' => __DIR__ . '/includes/installer/WebInstallerComplete.php', diff --git a/composer.json b/composer.json index 7031cad0c6..a5501d080a 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,7 @@ "monolog/monolog": "~1.22.1", "nikic/php-parser": "2.1.0", "nmred/kafka-php": "0.1.5", - "phpunit/phpunit": "4.8.35", + "phpunit/phpunit": "4.8.36", "psy/psysh": "0.8.11", "wikimedia/avro": "1.7.7", "wikimedia/testing-access-wrapper": "~1.0", @@ -79,7 +79,8 @@ }, "autoload": { "psr-0": { - "ComposerHookHandler": "includes/composer" + "ComposerHookHandler": "includes/composer", + "ComposerVendorHtaccessCreator": "includes/composer" }, "files": [ "includes/compat/Timestamp.php" @@ -97,6 +98,8 @@ "fix": "phpcbf", "pre-install-cmd": "ComposerHookHandler::onPreInstall", "pre-update-cmd": "ComposerHookHandler::onPreUpdate", + "post-install-cmd": "ComposerVendorHtaccessCreator::onEvent", + "post-update-cmd": "ComposerVendorHtaccessCreator::onEvent", "test": [ "composer lint", "composer phpcs" diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index d9f032c39c..3cd7ef181a 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -5785,7 +5785,7 @@ $wgPasswordAttemptThrottle = [ ]; /** - * @var Array Map of (grant => right => boolean) + * @var array Map of (grant => right => boolean) * Users authorize consumers (like Apps) to act on their behalf but only with * a subset of the user's normal account rights (signed off on by the user). * The possible rights to grant to a consumer are bundled into groups called @@ -5887,7 +5887,7 @@ $wgGrantPermissions['createaccount']['createaccount'] = true; $wgGrantPermissions['privateinfo']['viewmyprivateinfo'] = true; /** - * @var Array Map of grants to their UI grouping + * @var array Map of grants to their UI grouping * @since 1.27 */ $wgGrantPermissionGroups = [ diff --git a/includes/EditPage.php b/includes/EditPage.php index 4260c99324..ff224c5598 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -3288,7 +3288,7 @@ class EditPage { protected function showFormBeforeText() { $out = $this->context->getOutput(); - $out->addHTML( Html::hidden( 'wpSection', htmlspecialchars( $this->section ) ) ); + $out->addHTML( Html::hidden( 'wpSection', $this->section ) ); $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) ); $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) ); $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) ); diff --git a/includes/Feed.php b/includes/Feed.php index bc7747fe72..fd223e63dd 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -230,6 +230,12 @@ abstract class ChannelFeed extends FeedItem { $wgOut->disable(); $mimetype = $this->contentType(); header( "Content-type: $mimetype; charset=UTF-8" ); + + // Set a sane filename + $exts = MimeMagic::singleton()->getExtensionsForType( $mimetype ); + $ext = $exts ? strtok( $exts, ' ' ) : 'xml'; + header( "Content-Disposition: inline; filename=\"feed.{$ext}\"" ); + if ( $wgVaryOnXFP ) { $wgOut->addVaryHeader( 'X-Forwarded-Proto' ); } diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 1cff881c21..bb1951d528 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -2225,7 +2225,23 @@ function wfPercent( $nr, $acc = 2, $round = true ) { * @return bool */ function wfIniGetBool( $setting ) { - $val = strtolower( ini_get( $setting ) ); + return wfStringToBool( ini_get( $setting ) ); +} + +/** + * Convert string value to boolean, when the following are interpreted as true: + * - on + * - true + * - yes + * - Any number, except 0 + * All other strings are interpreted as false. + * + * @param string $val + * @return bool + * @since 1.31 + */ +function wfStringToBool( $val ) { + $val = strtolower( $val ); // 'on' and 'true' can't have whitespace around them, but '1' can. return $val == 'on' || $val == 'true' @@ -3487,3 +3503,37 @@ function wfArrayPlus2d( array $baseArray, array $newValues ) { return $baseArray; } + +/** + * Get system resource usage of current request context. + * Invokes the getrusage(2) system call, requesting RUSAGE_SELF if on PHP5 + * or RUSAGE_THREAD if on HHVM. Returns false if getrusage is not available. + * + * @since 1.24 + * @return array|bool Resource usage data or false if no data available. + */ +function wfGetRusage() { + if ( !function_exists( 'getrusage' ) ) { + return false; + } elseif ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) { + return getrusage( 2 /* RUSAGE_THREAD */ ); + } else { + return getrusage( 0 /* RUSAGE_SELF */ ); + } +} + +/** + * Begin profiling of a function + * @param string $functionname Name of the function we will profile + * @deprecated since 1.25 + */ +function wfProfileIn( $functionname ) { +} + +/** + * Stop profiling of a function + * @param string $functionname Name of the function we have profiled + * @deprecated since 1.25 + */ +function wfProfileOut( $functionname = 'missing' ) { +} diff --git a/includes/Html.php b/includes/Html.php index 0988b0549e..524fdcd7d9 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -675,6 +675,52 @@ class Html { return self::input( $name, $value, 'checkbox', $attribs ); } + /** + * Return the HTML for a message box. + * @since 1.31 + * @param string $html of contents of box + * @param string $className corresponding to box + * @param string $heading (optional) + * @return string of HTML representing a box. + */ + public static function messageBox( $html, $className, $heading = '' ) { + if ( $heading ) { + $html = self::element( 'h2', [], $heading ) . $html; + } + return self::rawElement( 'div', [ 'class' => $className ], $html ); + } + + /** + * Return a warning box. + * @since 1.31 + * @param string $html of contents of box + * @return string of HTML representing a warning box. + */ + public static function warningBox( $html ) { + return self::messageBox( $html, 'warningbox' ); + } + + /** + * Return an error box. + * @since 1.31 + * @param string $html of contents of error box + * @param string $heading (optional) + * @return string of HTML representing an error box. + */ + public static function errorBox( $html, $heading = '' ) { + return self::messageBox( $html, 'errorbox', $heading ); + } + + /** + * Return a success box. + * @since 1.31 + * @param string $html of contents of box + * @return string of HTML representing a success box. + */ + public static function successBox( $html ) { + return self::messageBox( $html, 'successbox' ); + } + /** * Convenience function to produce a radio button (input element with type=radio) * diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index 0d010b49d1..19b71f12e1 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -31,7 +31,7 @@ use SearchEngineConfig; use SearchEngineFactory; use SiteLookup; use SiteStore; -use WatchedItemStore; +use WatchedItemStoreInterface; use WatchedItemQueryService; use SkinFactory; use TitleFormatter; @@ -513,7 +513,7 @@ class MediaWikiServices extends ServiceContainer { /** * @since 1.28 - * @return WatchedItemStore + * @return WatchedItemStoreInterface */ public function getWatchedItemStore() { return $this->getService( 'WatchedItemStore' ); @@ -690,6 +690,14 @@ class MediaWikiServices extends ServiceContainer { return $this->getService( 'ShellCommandFactory' ); } + /** + * @since 1.31 + * @return \ExternalStoreFactory + */ + public function getExternalStoreFactory() { + return $this->getService( 'ExternalStoreFactory' ); + } + /////////////////////////////////////////////////////////////////////////// // NOTE: When adding a service getter here, don't forget to add a test // case for it in MediaWikiServicesTest::provideGetters() and in diff --git a/includes/Message.php b/includes/Message.php index 2a55d0ee74..3b2f3ccc7b 100644 --- a/includes/Message.php +++ b/includes/Message.php @@ -1123,11 +1123,29 @@ class Message implements MessageSpecifier, Serializable { * @return string */ protected function replaceParameters( $message, $type = 'before', $format ) { + // A temporary marker for $1 parameters that is only valid + // in non-attribute contexts. However if the entire message is escaped + // then we don't want to use it because it will be mangled in all contexts + // and its unnessary as ->escaped() messages aren't html. + $marker = $format === self::FORMAT_ESCAPED ? '$' : '$\'"'; $replacementKeys = []; foreach ( $this->parameters as $n => $param ) { list( $paramType, $value ) = $this->extractParam( $param, $format ); - if ( $type === $paramType ) { - $replacementKeys['$' . ( $n + 1 )] = $value; + if ( $type === 'before' ) { + if ( $paramType === 'before' ) { + $replacementKeys['$' . ( $n + 1 )] = $value; + } else /* $paramType === 'after' */ { + // To protect against XSS from replacing parameters + // inside html attributes, we convert $1 to $'"1. + // In the event that one of the parameters ends up + // in an attribute, either the ' or the " will be + // escaped, breaking the replacement and avoiding XSS. + $replacementKeys['$' . ( $n + 1 )] = $marker . ( $n + 1 ); + } + } else { + if ( $paramType === 'after' ) { + $replacementKeys[$marker . ( $n + 1 )] = $value; + } } } $message = strtr( $message, $replacementKeys ); diff --git a/includes/Preferences.php b/includes/Preferences.php index 94854fa23d..e383f03f6b 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -47,9 +47,6 @@ use MediaWiki\MediaWikiServices; * over to the tryUISubmit static method of this class. */ class Preferences { - /** @var array */ - protected static $defaultPreferences = null; - /** @var array */ protected static $saveFilters = [ 'timecorrection' => [ 'Preferences', 'filterTimezoneInput' ], @@ -78,9 +75,10 @@ class Preferences { * @return array|null */ static function getPreferences( $user, IContextSource $context ) { - if ( self::$defaultPreferences ) { - return self::$defaultPreferences; - } + OutputPage::setupOOUI( + strtolower( $context->getSkin()->getSkinName() ), + $context->getLanguage()->getDir() + ); $defaultPreferences = []; @@ -98,7 +96,6 @@ class Preferences { Hooks::run( 'GetPreferences', [ $user, &$defaultPreferences ] ); self::loadPreferenceValues( $user, $context, $defaultPreferences ); - self::$defaultPreferences = $defaultPreferences; return $defaultPreferences; } @@ -320,14 +317,17 @@ class Preferences { if ( $canEditPrivateInfo && $authManager->allowsAuthenticationDataChange( new PasswordAuthenticationRequest(), false )->isGood() ) { - $link = $linkRenderer->makeLink( SpecialPage::getTitleFor( 'ChangePassword' ), - $context->msg( 'prefs-resetpass' )->text(), [], - [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ); + $link = new OOUI\ButtonWidget( [ + 'href' => SpecialPage::getTitleFor( 'ChangePassword' )->getLinkURL( [ + 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() + ] ), + 'label' => $context->msg( 'prefs-resetpass' )->text(), + ] ); $defaultPreferences['password'] = [ 'type' => 'info', 'raw' => true, - 'default' => $link, + 'default' => (string)$link, 'label-message' => 'yourpassword', 'section' => 'personal/info', ]; @@ -471,16 +471,15 @@ class Preferences { $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : ''; if ( $canEditPrivateInfo && $authManager->allowsPropertyChange( 'emailaddress' ) ) { - $link = $linkRenderer->makeLink( - SpecialPage::getTitleFor( 'ChangeEmail' ), - $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(), - [], - [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ); - - $emailAddress .= $emailAddress == '' ? $link : ( - $context->msg( 'word-separator' )->escaped() - . $context->msg( 'parentheses' )->rawParams( $link )->escaped() - ); + $link = new OOUI\ButtonWidget( [ + 'href' => SpecialPage::getTitleFor( 'ChangeEmail' )->getLinkURL( [ + 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() + ] ), + 'label' => + $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->text(), + ] ); + + $emailAddress .= $emailAddress == '' ? $link : ( '
' . $link ); } $defaultPreferences['emailaddress'] = [ @@ -515,10 +514,10 @@ class Preferences { } else { $disableEmailPrefs = true; $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '
' . - $linkRenderer->makeKnownLink( - SpecialPage::getTitleFor( 'Confirmemail' ), - $context->msg( 'emailconfirmlink' )->text() - ) . '
'; + new OOUI\ButtonWidget( [ + 'href' => SpecialPage::getTitleFor( 'Confirmemail' )->getLinkURL(), + 'label' => $context->msg( 'emailconfirmlink' )->text(), + ] ); $emailauthenticationclass = "mw-email-not-authenticated"; } } else { @@ -755,6 +754,7 @@ class Preferences { 'default' => $tzSetting, 'size' => 20, 'section' => 'rendering/timeoffset', + 'id' => 'wpTimeCorrection', ]; } @@ -997,7 +997,7 @@ class Preferences { # # Watchlist ##################################### if ( $user->isAllowed( 'editmywatchlist' ) ) { - $editWatchlistLinks = []; + $editWatchlistLinks = ''; $editWatchlistModes = [ 'edit' => [ 'EditWatchlist', false ], 'raw' => [ 'EditWatchlist', 'raw' ], @@ -1006,16 +1006,19 @@ class Preferences { $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) { // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear - $editWatchlistLinks[] = $linkRenderer->makeKnownLink( - SpecialPage::getTitleFor( $mode[0], $mode[1] ), - new HtmlArmor( $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() ) - ); + $editWatchlistLinks .= + new OOUI\ButtonWidget( [ + 'href' => SpecialPage::getTitleFor( $mode[0], $mode[1] )->getLinkURL(), + 'label' => new OOUI\HtmlSnippet( + $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() + ), + ] ); } $defaultPreferences['editwatchlist'] = [ 'type' => 'info', 'raw' => true, - 'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ), + 'default' => $editWatchlistLinks, 'label-message' => 'prefs-editwatchlist-label', 'section' => 'watchlist/editwatchlist', ]; @@ -1138,6 +1141,12 @@ class Preferences { 'default' => $user->getTokenFromOption( 'watchlisttoken' ), 'help-message' => 'prefs-help-watchlist-token2', ]; + $defaultPreferences['watchlisttoken-info2'] = [ + 'type' => 'info', + 'section' => 'watchlist/tokenwatchlist', + 'raw' => true, + 'default' => $context->msg( 'prefs-help-watchlist-token2' )->parse(), + ]; } } @@ -1358,6 +1367,9 @@ class Preferences { $formClass = 'PreferencesForm', array $remove = [] ) { + // We use ButtonWidgets in some of the getPreferences() functions + $context->getOutput()->enableOOUI(); + $formDescriptor = self::getPreferences( $user, $context ); if ( count( $remove ) ) { $removeKeys = array_flip( $remove ); diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php deleted file mode 100644 index 4c996771e8..0000000000 --- a/includes/Sanitizer.php +++ /dev/null @@ -1,2115 +0,0 @@ - et al - * https://www.mediawiki.org/ - * - * 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 Parser - */ - -/** - * HTML sanitizer for MediaWiki - * @ingroup Parser - */ -class Sanitizer { - /** - * Regular expression to match various types of character references in - * Sanitizer::normalizeCharReferences and Sanitizer::decodeCharReferences - */ - const CHAR_REFS_REGEX = - '/&([A-Za-z0-9\x80-\xff]+); - |&\#([0-9]+); - |&\#[xX]([0-9A-Fa-f]+); - |(&)/x'; - - /** - * Acceptable tag name charset from HTML5 parsing spec - * https://www.w3.org/TR/html5/syntax.html#tag-open-state - */ - const ELEMENT_BITS_REGEX = '!^(/?)([A-Za-z][^\t\n\v />\0]*+)([^>]*?)(/?>)([^<]*)$!'; - - /** - * Blacklist for evil uris like javascript: - * WARNING: DO NOT use this in any place that actually requires blacklisting - * for security reasons. There are NUMEROUS[1] ways to bypass blacklisting, the - * only way to be secure from javascript: uri based xss vectors is to whitelist - * things that you know are safe and deny everything else. - * [1]: http://ha.ckers.org/xss.html - */ - const EVIL_URI_PATTERN = '!(^|\s|\*/\s*)(javascript|vbscript)([^\w]|$)!i'; - const XMLNS_ATTRIBUTE_PATTERN = "/^xmlns:[:A-Z_a-z-.0-9]+$/"; - - /** - * Tells escapeUrlForHtml() to encode the ID using the wiki's primary encoding. - * - * @since 1.30 - */ - const ID_PRIMARY = 0; - - /** - * Tells escapeUrlForHtml() to encode the ID using the fallback encoding, or return false - * if no fallback is configured. - * - * @since 1.30 - */ - const ID_FALLBACK = 1; - - /** - * List of all named character entities defined in HTML 4.01 - * https://www.w3.org/TR/html4/sgml/entities.html - * As well as ' which is only defined starting in XHTML1. - */ - private static $htmlEntities = [ - 'Aacute' => 193, - 'aacute' => 225, - 'Acirc' => 194, - 'acirc' => 226, - 'acute' => 180, - 'AElig' => 198, - 'aelig' => 230, - 'Agrave' => 192, - 'agrave' => 224, - 'alefsym' => 8501, - 'Alpha' => 913, - 'alpha' => 945, - 'amp' => 38, - 'and' => 8743, - 'ang' => 8736, - 'apos' => 39, // New in XHTML & HTML 5; avoid in output for compatibility with IE. - 'Aring' => 197, - 'aring' => 229, - 'asymp' => 8776, - 'Atilde' => 195, - 'atilde' => 227, - 'Auml' => 196, - 'auml' => 228, - 'bdquo' => 8222, - 'Beta' => 914, - 'beta' => 946, - 'brvbar' => 166, - 'bull' => 8226, - 'cap' => 8745, - 'Ccedil' => 199, - 'ccedil' => 231, - 'cedil' => 184, - 'cent' => 162, - 'Chi' => 935, - 'chi' => 967, - 'circ' => 710, - 'clubs' => 9827, - 'cong' => 8773, - 'copy' => 169, - 'crarr' => 8629, - 'cup' => 8746, - 'curren' => 164, - 'dagger' => 8224, - 'Dagger' => 8225, - 'darr' => 8595, - 'dArr' => 8659, - 'deg' => 176, - 'Delta' => 916, - 'delta' => 948, - 'diams' => 9830, - 'divide' => 247, - 'Eacute' => 201, - 'eacute' => 233, - 'Ecirc' => 202, - 'ecirc' => 234, - 'Egrave' => 200, - 'egrave' => 232, - 'empty' => 8709, - 'emsp' => 8195, - 'ensp' => 8194, - 'Epsilon' => 917, - 'epsilon' => 949, - 'equiv' => 8801, - 'Eta' => 919, - 'eta' => 951, - 'ETH' => 208, - 'eth' => 240, - 'Euml' => 203, - 'euml' => 235, - 'euro' => 8364, - 'exist' => 8707, - 'fnof' => 402, - 'forall' => 8704, - 'frac12' => 189, - 'frac14' => 188, - 'frac34' => 190, - 'frasl' => 8260, - 'Gamma' => 915, - 'gamma' => 947, - 'ge' => 8805, - 'gt' => 62, - 'harr' => 8596, - 'hArr' => 8660, - 'hearts' => 9829, - 'hellip' => 8230, - 'Iacute' => 205, - 'iacute' => 237, - 'Icirc' => 206, - 'icirc' => 238, - 'iexcl' => 161, - 'Igrave' => 204, - 'igrave' => 236, - 'image' => 8465, - 'infin' => 8734, - 'int' => 8747, - 'Iota' => 921, - 'iota' => 953, - 'iquest' => 191, - 'isin' => 8712, - 'Iuml' => 207, - 'iuml' => 239, - 'Kappa' => 922, - 'kappa' => 954, - 'Lambda' => 923, - 'lambda' => 955, - 'lang' => 9001, - 'laquo' => 171, - 'larr' => 8592, - 'lArr' => 8656, - 'lceil' => 8968, - 'ldquo' => 8220, - 'le' => 8804, - 'lfloor' => 8970, - 'lowast' => 8727, - 'loz' => 9674, - 'lrm' => 8206, - 'lsaquo' => 8249, - 'lsquo' => 8216, - 'lt' => 60, - 'macr' => 175, - 'mdash' => 8212, - 'micro' => 181, - 'middot' => 183, - 'minus' => 8722, - 'Mu' => 924, - 'mu' => 956, - 'nabla' => 8711, - 'nbsp' => 160, - 'ndash' => 8211, - 'ne' => 8800, - 'ni' => 8715, - 'not' => 172, - 'notin' => 8713, - 'nsub' => 8836, - 'Ntilde' => 209, - 'ntilde' => 241, - 'Nu' => 925, - 'nu' => 957, - 'Oacute' => 211, - 'oacute' => 243, - 'Ocirc' => 212, - 'ocirc' => 244, - 'OElig' => 338, - 'oelig' => 339, - 'Ograve' => 210, - 'ograve' => 242, - 'oline' => 8254, - 'Omega' => 937, - 'omega' => 969, - 'Omicron' => 927, - 'omicron' => 959, - 'oplus' => 8853, - 'or' => 8744, - 'ordf' => 170, - 'ordm' => 186, - 'Oslash' => 216, - 'oslash' => 248, - 'Otilde' => 213, - 'otilde' => 245, - 'otimes' => 8855, - 'Ouml' => 214, - 'ouml' => 246, - 'para' => 182, - 'part' => 8706, - 'permil' => 8240, - 'perp' => 8869, - 'Phi' => 934, - 'phi' => 966, - 'Pi' => 928, - 'pi' => 960, - 'piv' => 982, - 'plusmn' => 177, - 'pound' => 163, - 'prime' => 8242, - 'Prime' => 8243, - 'prod' => 8719, - 'prop' => 8733, - 'Psi' => 936, - 'psi' => 968, - 'quot' => 34, - 'radic' => 8730, - 'rang' => 9002, - 'raquo' => 187, - 'rarr' => 8594, - 'rArr' => 8658, - 'rceil' => 8969, - 'rdquo' => 8221, - 'real' => 8476, - 'reg' => 174, - 'rfloor' => 8971, - 'Rho' => 929, - 'rho' => 961, - 'rlm' => 8207, - 'rsaquo' => 8250, - 'rsquo' => 8217, - 'sbquo' => 8218, - 'Scaron' => 352, - 'scaron' => 353, - 'sdot' => 8901, - 'sect' => 167, - 'shy' => 173, - 'Sigma' => 931, - 'sigma' => 963, - 'sigmaf' => 962, - 'sim' => 8764, - 'spades' => 9824, - 'sub' => 8834, - 'sube' => 8838, - 'sum' => 8721, - 'sup' => 8835, - 'sup1' => 185, - 'sup2' => 178, - 'sup3' => 179, - 'supe' => 8839, - 'szlig' => 223, - 'Tau' => 932, - 'tau' => 964, - 'there4' => 8756, - 'Theta' => 920, - 'theta' => 952, - 'thetasym' => 977, - 'thinsp' => 8201, - 'THORN' => 222, - 'thorn' => 254, - 'tilde' => 732, - 'times' => 215, - 'trade' => 8482, - 'Uacute' => 218, - 'uacute' => 250, - 'uarr' => 8593, - 'uArr' => 8657, - 'Ucirc' => 219, - 'ucirc' => 251, - 'Ugrave' => 217, - 'ugrave' => 249, - 'uml' => 168, - 'upsih' => 978, - 'Upsilon' => 933, - 'upsilon' => 965, - 'Uuml' => 220, - 'uuml' => 252, - 'weierp' => 8472, - 'Xi' => 926, - 'xi' => 958, - 'Yacute' => 221, - 'yacute' => 253, - 'yen' => 165, - 'Yuml' => 376, - 'yuml' => 255, - 'Zeta' => 918, - 'zeta' => 950, - 'zwj' => 8205, - 'zwnj' => 8204 - ]; - - /** - * Character entity aliases accepted by MediaWiki - */ - private static $htmlEntityAliases = [ - 'רלמ' => 'rlm', - 'رلم' => 'rlm', - ]; - - /** - * Lazy-initialised attributes regex, see getAttribsRegex() - */ - private static $attribsRegex; - - /** - * Regular expression to match HTML/XML attribute pairs within a tag. - * Allows some... latitude. Based on, - * https://www.w3.org/TR/html5/syntax.html#before-attribute-value-state - * Used in Sanitizer::fixTagAttributes and Sanitizer::decodeTagAttributes - * @return string - */ - static function getAttribsRegex() { - if ( self::$attribsRegex === null ) { - $attribFirst = "[:_\p{L}\p{N}]"; - $attrib = "[:_\.\-\p{L}\p{N}]"; - $space = '[\x09\x0a\x0c\x0d\x20]'; - self::$attribsRegex = - "/(?:^|$space)({$attribFirst}{$attrib}*) - ($space*=$space* - (?: - # The attribute value: quoted or alone - \"([^\"]*)(?:\"|\$) - | '([^']*)(?:'|\$) - | (((?!$space|>).)*) - ) - )?(?=$space|\$)/sxu"; - } - return self::$attribsRegex; - } - - /** - * Return the various lists of recognized tags - * @param array $extratags For any extra tags to include - * @param array $removetags For any tags (default or extra) to exclude - * @return array - */ - public static function getRecognizedTagData( $extratags = [], $removetags = [] ) { - global $wgAllowImageTag; - - static $htmlpairsStatic, $htmlsingle, $htmlsingleonly, $htmlnest, $tabletags, - $htmllist, $listtags, $htmlsingleallowed, $htmlelementsStatic, $staticInitialised; - - // Base our staticInitialised variable off of the global config state so that if the globals - // are changed (like in the screwed up test system) we will re-initialise the settings. - $globalContext = $wgAllowImageTag; - if ( !$staticInitialised || $staticInitialised != $globalContext ) { - $htmlpairsStatic = [ # Tags that must be closed - 'b', 'bdi', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1', - 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's', - 'strike', 'strong', 'tt', 'var', 'div', 'center', - 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre', - 'ruby', 'rb', 'rp', 'rt', 'rtc', 'p', 'span', 'abbr', 'dfn', - 'kbd', 'samp', 'data', 'time', 'mark' - ]; - $htmlsingle = [ - 'br', 'wbr', 'hr', 'li', 'dt', 'dd', 'meta', 'link' - ]; - - # Elements that cannot have close tags. This is (not coincidentally) - # also the list of tags for which the HTML 5 parsing algorithm - # requires you to "acknowledge the token's self-closing flag", i.e. - # a self-closing tag like
is not an HTML 5 parse error only - # for this list. - $htmlsingleonly = [ - 'br', 'wbr', 'hr', 'meta', 'link' - ]; - - $htmlnest = [ # Tags that can be nested--?? - 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul', - 'li', 'dl', 'dt', 'dd', 'font', 'big', 'small', 'sub', 'sup', 'span', - 'var', 'kbd', 'samp', 'em', 'strong', 'q', 'ruby', 'bdo' - ]; - $tabletags = [ # Can only appear inside table, we will close them - 'td', 'th', 'tr', - ]; - $htmllist = [ # Tags used by list - 'ul', 'ol', - ]; - $listtags = [ # Tags that can appear in a list - 'li', - ]; - - if ( $wgAllowImageTag ) { - $htmlsingle[] = 'img'; - $htmlsingleonly[] = 'img'; - } - - $htmlsingleallowed = array_unique( array_merge( $htmlsingle, $tabletags ) ); - $htmlelementsStatic = array_unique( array_merge( $htmlsingle, $htmlpairsStatic, $htmlnest ) ); - - # Convert them all to hashtables for faster lookup - $vars = [ 'htmlpairsStatic', 'htmlsingle', 'htmlsingleonly', 'htmlnest', 'tabletags', - 'htmllist', 'listtags', 'htmlsingleallowed', 'htmlelementsStatic' ]; - foreach ( $vars as $var ) { - $$var = array_flip( $$var ); - } - $staticInitialised = $globalContext; - } - - # Populate $htmlpairs and $htmlelements with the $extratags and $removetags arrays - $extratags = array_flip( $extratags ); - $removetags = array_flip( $removetags ); - $htmlpairs = array_merge( $extratags, $htmlpairsStatic ); - $htmlelements = array_diff_key( array_merge( $extratags, $htmlelementsStatic ), $removetags ); - - return [ - 'htmlpairs' => $htmlpairs, - 'htmlsingle' => $htmlsingle, - 'htmlsingleonly' => $htmlsingleonly, - 'htmlnest' => $htmlnest, - 'tabletags' => $tabletags, - 'htmllist' => $htmllist, - 'listtags' => $listtags, - 'htmlsingleallowed' => $htmlsingleallowed, - 'htmlelements' => $htmlelements, - ]; - } - - /** - * Cleans up HTML, removes dangerous tags and attributes, and - * removes HTML comments - * @param string $text - * @param callable $processCallback Callback to do any variable or parameter - * replacements in HTML attribute values - * @param array|bool $args Arguments for the processing callback - * @param array $extratags For any extra tags to include - * @param array $removetags For any tags (default or extra) to exclude - * @param callable $warnCallback (Deprecated) Callback allowing the - * addition of a tracking category when bad input is encountered. - * DO NOT ADD NEW PARAMETERS AFTER $warnCallback, since it will be - * removed shortly. - * @return string - */ - public static function removeHTMLtags( $text, $processCallback = null, - $args = [], $extratags = [], $removetags = [], $warnCallback = null - ) { - extract( self::getRecognizedTagData( $extratags, $removetags ) ); - - # Remove HTML comments - $text = self::removeHTMLcomments( $text ); - $bits = explode( '<', $text ); - $text = str_replace( '>', '>', array_shift( $bits ) ); - if ( !MWTidy::isEnabled() ) { - $tagstack = $tablestack = []; - foreach ( $bits as $x ) { - $regs = []; - # $slash: Does the current element start with a '/'? - # $t: Current element name - # $params: String between element name and > - # $brace: Ending '>' or '/>' - # $rest: Everything until the next element of $bits - if ( preg_match( self::ELEMENT_BITS_REGEX, $x, $regs ) ) { - list( /* $qbar */, $slash, $t, $params, $brace, $rest ) = $regs; - } else { - $slash = $t = $params = $brace = $rest = null; - } - - $badtag = false; - $t = strtolower( $t ); - if ( isset( $htmlelements[$t] ) ) { - # Check our stack - if ( $slash && isset( $htmlsingleonly[$t] ) ) { - $badtag = true; - } elseif ( $slash ) { - # Closing a tag... is it the one we just opened? - MediaWiki\suppressWarnings(); - $ot = array_pop( $tagstack ); - MediaWiki\restoreWarnings(); - - if ( $ot != $t ) { - if ( isset( $htmlsingleallowed[$ot] ) ) { - # Pop all elements with an optional close tag - # and see if we find a match below them - $optstack = []; - array_push( $optstack, $ot ); - MediaWiki\suppressWarnings(); - $ot = array_pop( $tagstack ); - MediaWiki\restoreWarnings(); - while ( $ot != $t && isset( $htmlsingleallowed[$ot] ) ) { - array_push( $optstack, $ot ); - MediaWiki\suppressWarnings(); - $ot = array_pop( $tagstack ); - MediaWiki\restoreWarnings(); - } - if ( $t != $ot ) { - # No match. Push the optional elements back again - $badtag = true; - MediaWiki\suppressWarnings(); - $ot = array_pop( $optstack ); - MediaWiki\restoreWarnings(); - while ( $ot ) { - array_push( $tagstack, $ot ); - MediaWiki\suppressWarnings(); - $ot = array_pop( $optstack ); - MediaWiki\restoreWarnings(); - } - } - } else { - MediaWiki\suppressWarnings(); - array_push( $tagstack, $ot ); - MediaWiki\restoreWarnings(); - - #
  • can be nested in