Merge "Move OrderedStreamingForkController class from CirrusSearch to core."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 7 Sep 2017 18:27:49 +0000 (18:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 7 Sep 2017 18:27:49 +0000 (18:27 +0000)
268 files changed:
RELEASE-NOTES-1.30
autoload.php
composer.json
docs/hooks.txt
includes/CommentStore.php
includes/DefaultSettings.php
includes/Defines.php
includes/EditPage.php
includes/MovePage.php
includes/Revision.php
includes/Title.php
includes/actions/CreditsAction.php
includes/actions/InfoAction.php
includes/api/ApiEditPage.php
includes/api/ApiQuery.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryCategoryInfo.php
includes/api/ApiQueryDuplicateFiles.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryQueryPage.php
includes/api/ApiQueryRevisions.php
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiUpload.php
includes/api/i18n/gl.json
includes/api/i18n/ja.json
includes/api/i18n/pt-br.json
includes/changes/CategoryMembershipChange.php
includes/changes/EnhancedChangesList.php
includes/changes/RecentChange.php
includes/collation/CollationFa.php
includes/deferred/DeferredUpdates.php
includes/diff/DifferenceEngine.php
includes/exception/MWExceptionHandler.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/installer/DatabaseUpdater.php
includes/installer/MssqlInstaller.php
includes/installer/MysqlInstaller.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresInstaller.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/pt-br.json
includes/installer/i18n/vi.json
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/stats/SamplingStatsdClient.php
includes/libs/virtualrest/RestbaseVirtualRESTService.php
includes/logging/LogEntry.php
includes/logging/LogPage.php
includes/media/TransformationalImageHandler.php
includes/page/PageArchive.php
includes/page/WikiPage.php
includes/rcfeed/FormattedRCFeed.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/revisiondelete/RevDelArchivedFileItem.php
includes/revisiondelete/RevDelList.php
includes/skins/Skin.php
includes/specialpage/AuthManagerSpecialPage.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialBlock.php
includes/specials/SpecialChangeContentModel.php
includes/specials/SpecialContributions.php
includes/specials/SpecialNewimages.php
includes/specials/SpecialUpload.php
includes/specials/SpecialWatchlist.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/ProtectedPagesPager.php
includes/title/MediaWikiTitleCodec.php
includes/user/User.php
languages/i18n/ais.json
languages/i18n/ar.json
languages/i18n/as.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/cs.json
languages/i18n/da.json
languages/i18n/de.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gu.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/hu.json
languages/i18n/id.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/kab.json
languages/i18n/ko.json
languages/i18n/ku-latn.json
languages/i18n/lb.json
languages/i18n/lv.json
languages/i18n/mk.json
languages/i18n/mwl.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/or.json
languages/i18n/pl.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/skr-arab.json
languages/i18n/sl.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/su.json
languages/i18n/sv.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/archives/patch-bot_passwords-bp_user-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-change_tag-ct_log_id-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-change_tag-ct_rev_id-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-l10n_cache-primary-key.sql [new file with mode: 0644]
maintenance/archives/patch-page_restrictions-pr_user-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-tag_summary-ts_log_id-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-tag_summary-ts_rev_id-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-user-newtalk-userid-unsigned.sql [deleted file]
maintenance/archives/patch-user_newtalk-user_id-unsigned.sql [new file with mode: 0644]
maintenance/archives/patch-user_properties-up_user-unsigned.sql [new file with mode: 0644]
maintenance/deleteOldRevisions.php
maintenance/deleteOrphanedRevisions.php
maintenance/populateIpChanges.php [new file with mode: 0644]
maintenance/sqlite/archives/patch-l10n_cache-primary-key.sql [new file with mode: 0644]
maintenance/tables.sql
resources/lib/oojs-ui/i18n/ais.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/ar.json
resources/lib/oojs-ui/i18n/ast.json
resources/lib/oojs-ui/i18n/be-tarask.json
resources/lib/oojs-ui/i18n/bn.json
resources/lib/oojs-ui/i18n/br.json
resources/lib/oojs-ui/i18n/cs.json
resources/lib/oojs-ui/i18n/da.json
resources/lib/oojs-ui/i18n/de.json
resources/lib/oojs-ui/i18n/es.json
resources/lib/oojs-ui/i18n/eu.json
resources/lib/oojs-ui/i18n/fa.json
resources/lib/oojs-ui/i18n/fr.json
resources/lib/oojs-ui/i18n/gl.json
resources/lib/oojs-ui/i18n/he.json
resources/lib/oojs-ui/i18n/it.json
resources/lib/oojs-ui/i18n/kab.json
resources/lib/oojs-ui/i18n/ko.json
resources/lib/oojs-ui/i18n/lb.json
resources/lib/oojs-ui/i18n/mk.json
resources/lib/oojs-ui/i18n/ne.json
resources/lib/oojs-ui/i18n/nl.json
resources/lib/oojs-ui/i18n/pl.json
resources/lib/oojs-ui/i18n/pt.json
resources/lib/oojs-ui/i18n/qqq.json
resources/lib/oojs-ui/i18n/ru.json
resources/lib/oojs-ui/i18n/sr-ec.json
resources/lib/oojs-ui/i18n/su.json
resources/lib/oojs-ui/i18n/vi.json
resources/lib/oojs-ui/i18n/zh-hans.json
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-core-apex.css
resources/lib/oojs-ui/oojs-ui-core-wikimediaui.css
resources/lib/oojs-ui/oojs-ui-core.js
resources/lib/oojs-ui/oojs-ui-core.js.map
resources/lib/oojs-ui/oojs-ui-toolbars-apex.css
resources/lib/oojs-ui/oojs-ui-toolbars-wikimediaui.css
resources/lib/oojs-ui/oojs-ui-toolbars.js
resources/lib/oojs-ui/oojs-ui-toolbars.js.map
resources/lib/oojs-ui/oojs-ui-widgets-apex.css
resources/lib/oojs-ui/oojs-ui-widgets-wikimediaui.css
resources/lib/oojs-ui/oojs-ui-widgets.js
resources/lib/oojs-ui/oojs-ui-widgets.js.map
resources/lib/oojs-ui/oojs-ui-wikimediaui.js
resources/lib/oojs-ui/oojs-ui-windows-apex.css
resources/lib/oojs-ui/oojs-ui-windows-wikimediaui.css
resources/lib/oojs-ui/oojs-ui-windows.js
resources/lib/oojs-ui/themes/apex/icons-accessibility.json [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/icons-alerts.json
resources/lib/oojs-ui/themes/apex/icons-editing-advanced.json
resources/lib/oojs-ui/themes/apex/icons-interactions.json
resources/lib/oojs-ui/themes/apex/icons-movement.json
resources/lib/oojs-ui/themes/apex/images/icons/bright-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/bright-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/bright.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/bright.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/eye-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/eye-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/eyeClosed-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/eyeClosed-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/halfBright-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/halfBright-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/halfBright.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/halfBright.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-ltr-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-ltr-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-ltr.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-ltr.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-rtl-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-rtl-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-rtl.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/largerText-rtl.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/moon-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/moon-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/moon.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/moon.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/notBright-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/notBright-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/notBright.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/notBright.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-ltr-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-ltr-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-ltr.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-ltr.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-rtl-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-rtl-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-rtl.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/smallerText-rtl.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/visionSimulator-invert.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/visionSimulator-invert.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/visionSimulator.png [new file with mode: 0644]
resources/lib/oojs-ui/themes/apex/images/icons/visionSimulator.svg [new file with mode: 0644]
resources/lib/oojs-ui/themes/wikimediaui/icons-accessibility.json
resources/lib/oojs-ui/themes/wikimediaui/icons-alerts.json
resources/lib/oojs-ui/themes/wikimediaui/icons-editing-advanced.json
resources/lib/oojs-ui/themes/wikimediaui/icons-interactions.json
resources/lib/oojs-ui/themes/wikimediaui/icons-location.json
resources/lib/oojs-ui/themes/wikimediaui/icons-movement.json
resources/src/jquery/jquery.tablesorter.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuSectionOptionWidget.less
resources/src/mediawiki.widgets/mw.widgets.UsersMultiselectWidget.js
resources/src/mediawiki/mediawiki.js
tests/parser/ParserTestRunner.php
tests/parser/parserTests.txt
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/CommentStoreTest.php
tests/phpunit/includes/PageArchiveTest.php [new file with mode: 0644]
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/TitleMethodsTest.php
tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/includes/specials/ContribsPagerTest.php
tests/phpunit/includes/specials/SpecialPageDataTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/maintenance/backupTextPassTest.php
tests/phpunit/maintenance/backup_PageTest.php
tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js

index 93d52b8..f741a4e 100644 (file)
@@ -25,6 +25,8 @@ section).
   to plain class names, using the 'factory' key in the module description
   array. This allows dependency injection to be used for ResourceLoader modules.
 * $wgExceptionHooks has been removed.
+* (T163562) $wgRangeContributionsCIDRLimit was introduced to control the size
+  of IP ranges that can be queried at Special:Contributions.
 * (T45547) $wgUsePigLatinVariant added (off by default).
 * (T152540) MediaWiki now supports a section ID escaping style that allows to display
   non-Latin characters verbatim on many modern browsers. This is controlled by the
@@ -44,6 +46,8 @@ section).
 * (T37247) Output from Parser::parse() will now be wrapped in a div with
   class="mw-parser-output" by default. This may be changed or disabled using
   ParserOptions::setWrapOutputClass().
+* (T163562) Added ability to search for contributions within an IP ranges
+  at Special:Contributions.
 * Added 'ChangeTagsAllowedAdd' hook, enabling extensions to allow software-
   specific tags to be added by users.
 * Added a 'ParserOptionsRegister' hook to allow extensions to register
@@ -191,6 +195,8 @@ changes to languages because of Phabricator reports.
 * EditPage::isOouiEnabled() is deprecated and will always return true.
 * EditPage::getSummaryInput() and ::getSummaryInputOOUI() are deprecated. Please
   use ::getSummaryInputWidget() instead.
+* EditPage::getCheckboxes() and ::getCheckboxesOOUI() are deprecated. Please
+  use ::getCheckboxesWidget() instead.
 * Parser::getRandomString() (deprecated in 1.26) was removed.
 * Parser::uniqPrefix() (deprecated in 1.26) was removed.
 * Parser::extractTagsAndParams() now only accepts three arguments. The fourth,
@@ -201,6 +207,12 @@ changes to languages because of Phabricator reports.
   templatelinks, text, transcache, user_former_groups, user_properties.
 * IDatabase::nextSequenceValue() is no longer needed by any database backends
   (formerly it was needed by PostgreSQL and Oracle), and is now deprecated.
+* (T146591) The lc_lang_key index on the l10n_cache table has been changed into a
+  PRIMARY KEY.
+* (T157227) bot_password.bp_user, change_tag.ct_log_id, change_tag.ct_rev_id,
+  page_restrictions.pr_user, tag_summary.ts_log_id, tag_summary.ts_rev_id and
+  user_properties.up_user have all been made unsigned on MySQL.
+* DB_SLAVE is deprecated. DB_REPLICA should be used instead.
 
 == Compatibility ==
 MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for
index 531fa9c..5eba00b 100644 (file)
@@ -1118,6 +1118,7 @@ $wgAutoloadLocalClasses = [
        'PopulateFilearchiveSha1' => __DIR__ . '/maintenance/populateFilearchiveSha1.php',
        'PopulateImageSha1' => __DIR__ . '/maintenance/populateImageSha1.php',
        'PopulateInterwiki' => __DIR__ . '/maintenance/populateInterwiki.php',
+       'PopulateIpChanges' => __DIR__ . '/maintenance/populateIpChanges.php',
        'PopulateLogSearch' => __DIR__ . '/maintenance/populateLogSearch.php',
        'PopulateLogUsertext' => __DIR__ . '/maintenance/populateLogUsertext.php',
        'PopulatePPSortKey' => __DIR__ . '/maintenance/populatePPSortKey.php',
index a7983a0..dd7567c 100644 (file)
@@ -25,7 +25,7 @@
                "ext-xml": "*",
                "liuggio/statsd-php-client": "1.0.18",
                "mediawiki/at-ease": "1.1.0",
-               "oojs/oojs-ui": "0.22.5",
+               "oojs/oojs-ui": "0.23.0",
                "oyejorge/less.php": "1.7.0.14",
                "php": ">=5.5.9",
                "psr/log": "1.0.2",
index 8912b82..b7fe8c1 100644 (file)
@@ -1022,6 +1022,15 @@ When constructing them, you specify which group they belong to.  You can reuse
 existing groups (accessed through $special->getFilterGroup), or create your own
 (ChangesListBooleanFilterGroup or ChangesListStringOptionsFilterGroup).
 If you create new groups, you must register them with $special->registerFilterGroup.
+
+Note that this is called regardless of whether the user is currently using
+the new (structured) or old (unstructured) filter UI.  If you want your boolean
+filter to show on both the new and old UI, specify all the supported fields.
+These include showHide, label, and description.
+
+See the constructor of each ChangesList* class for documentation of supported
+fields.
+
 $special: ChangesListSpecialPage instance
 
 'ChangeTagAfterDelete': Called after a change tag has been deleted (that is,
index fdfa6d9..2ed21d1 100644 (file)
@@ -29,6 +29,12 @@ use Wikimedia\Rdbms\IDatabase;
  */
 class CommentStore {
 
+       /** Maximum length of a comment. Longer comments will be truncated. */
+       const MAX_COMMENT_LENGTH = 65535;
+
+       /** Maximum length of serialized data. Longer data will result in an exception. */
+       const MAX_DATA_LENGTH = 65535;
+
        /**
         * Define fields that use temporary tables for transitional purposes
         * @var array Keys are '$key', values are arrays with four fields:
@@ -68,15 +74,21 @@ class CommentStore {
        /** @var array|null Cache for `self::getJoin()` */
        protected $joinCache = null;
 
+       /** @var Language Language to use for comment truncation */
+       protected $lang;
+
        /**
         * @param string $key A key such as "rev_comment" identifying the comment
         *  field being fetched.
+        * @param Language $lang Language to use for comment truncation. Defaults
+        *  to $wgContLang.
         */
-       public function __construct( $key ) {
-               global $wgCommentTableSchemaMigrationStage;
+       public function __construct( $key, Language $lang = null ) {
+               global $wgCommentTableSchemaMigrationStage, $wgContLang;
 
                $this->key = $key;
                $this->stage = $wgCommentTableSchemaMigrationStage;
+               $this->lang = $lang ?: $wgContLang;
        }
 
        /**
@@ -376,6 +388,9 @@ class CommentStore {
                        }
                }
 
+               # Truncate comment in a Unicode-sensitive manner
+               $comment->text = $this->lang->truncate( $comment->text, self::MAX_COMMENT_LENGTH );
+
                if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
                        $dbData = $comment->data;
                        if ( !$comment->message instanceof RawMessage ) {
@@ -386,6 +401,11 @@ class CommentStore {
                        }
                        if ( $dbData !== null ) {
                                $dbData = FormatJson::encode( (object)$dbData, false, FormatJson::ALL_OK );
+                               $len = strlen( $dbData );
+                               if ( $len > self::MAX_DATA_LENGTH ) {
+                                       $max = self::MAX_DATA_LENGTH;
+                                       throw new OverflowException( "Comment data is too long ($len bytes, maximum is $max)" );
+                               }
                        }
 
                        $hash = self::hash( $comment->text, $dbData );
@@ -430,7 +450,7 @@ class CommentStore {
                $comment = $this->createComment( $dbw, $comment, $data );
 
                if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
-                       $fields[$this->key] = $comment->text;
+                       $fields[$this->key] = $this->lang->truncate( $comment->text, 255 );
                }
 
                if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
@@ -457,7 +477,7 @@ class CommentStore {
        }
 
        /**
-        * Prepare for the insertion of a row with a comment
+        * Insert a comment in preparation for a row that references it
         *
         * @note It's recommended to include both the call to this method and the
         *  row insert in the same transaction.
@@ -476,7 +496,7 @@ class CommentStore {
        }
 
        /**
-        * Prepare for the insertion of a row with a comment and temporary table
+        * Insert a comment in a temporary table in preparation for a row that references it
         *
         * This is currently needed for "rev_comment" and "img_description". In the
         * future that requirement will be removed.
index cf3e569..cf8e089 100644 (file)
@@ -8727,6 +8727,18 @@ $wgCSPFalsePositiveUrls = [
        'https://ad.lkqd.net/vpaid/vpaid.js' => true,
 ];
 
+/**
+ * Shortest CIDR limits that can be checked in any individual range check
+ * at Special:Contributions.
+ *
+ * @var array
+ * @since 1.30
+ */
+$wgRangeContributionsCIDRLimit = [
+       'IPv4' => 16,
+       'IPv6' => 32,
+];
+
 /**
  * The following variables define 3 user experience levels:
  *
index 8ac84e5..ca603e7 100644 (file)
@@ -31,6 +31,9 @@ use Wikimedia\Rdbms\IDatabase;
  */
 
 # Obsolete aliases
+/**
+ * @deprecated since 1.28
+ */
 define( 'DB_SLAVE', -1 );
 
 /**@{
index 6ea293c..06a5cc3 100644 (file)
@@ -511,6 +511,7 @@ class EditPage {
         * @deprecated since 1.29, call edit directly
         */
        public function submit() {
+               wfDeprecated( __METHOD__, '1.29' );
                $this->edit();
        }
 
@@ -526,7 +527,7 @@ class EditPage {
         * the newly-edited page.
         */
        public function edit() {
-               global $wgOut, $wgRequest, $wgUser;
+               global $wgRequest, $wgUser;
                // Allow extensions to modify/prevent this form or submission
                if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
                        return;
@@ -536,7 +537,7 @@ class EditPage {
 
                // If they used redlink=1 and the page exists, redirect to the main article
                if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
-                       $wgOut->redirect( $this->mTitle->getFullURL() );
+                       $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
                        return;
                }
 
@@ -701,13 +702,14 @@ class EditPage {
         * @throws PermissionsError
         */
        protected function displayPermissionsError( array $permErrors ) {
-               global $wgRequest, $wgOut;
+               global $wgRequest;
 
+               $out = $this->context->getOutput();
                if ( $wgRequest->getBool( 'redlink' ) ) {
                        // The edit page was reached via a red link.
                        // Redirect to the article page and let them click the edit tab if
                        // they really want a permission error.
-                       $wgOut->redirect( $this->mTitle->getFullURL() );
+                       $out->redirect( $this->mTitle->getFullURL() );
                        return;
                }
 
@@ -722,7 +724,7 @@ class EditPage {
 
                $this->displayViewSourcePage(
                        $content,
-                       $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' )
+                       $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
                );
        }
 
@@ -732,29 +734,28 @@ class EditPage {
         * @param string $errorMessage additional wikitext error message to display
         */
        protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
-               global $wgOut;
-
-               Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$wgOut ] );
+               $out = $this->context->getOutput();
+               Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
 
-               $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setPageTitle( $this->context->msg(
+               $out->setRobotPolicy( 'noindex,nofollow' );
+               $out->setPageTitle( $this->context->msg(
                        'viewsource-title',
                        $this->getContextTitle()->getPrefixedText()
                ) );
-               $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
-               $wgOut->addHTML( $this->editFormPageTop );
-               $wgOut->addHTML( $this->editFormTextTop );
+               $out->addBacklinkSubtitle( $this->getContextTitle() );
+               $out->addHTML( $this->editFormPageTop );
+               $out->addHTML( $this->editFormTextTop );
 
                if ( $errorMessage !== '' ) {
-                       $wgOut->addWikiText( $errorMessage );
-                       $wgOut->addHTML( "<hr />\n" );
+                       $out->addWikiText( $errorMessage );
+                       $out->addHTML( "<hr />\n" );
                }
 
                # If the user made changes, preserve them when showing the markup
                # (This happens when a user is blocked during edit, for instance)
                if ( !$this->firsttime ) {
                        $text = $this->textbox1;
-                       $wgOut->addWikiMsg( 'viewyourtext' );
+                       $out->addWikiMsg( 'viewyourtext' );
                } else {
                        try {
                                $text = $this->toEditText( $content );
@@ -763,20 +764,20 @@ class EditPage {
                                # (e.g. for an old revision with a different model)
                                $text = $content->serialize();
                        }
-                       $wgOut->addWikiMsg( 'viewsourcetext' );
+                       $out->addWikiMsg( 'viewsourcetext' );
                }
 
-               $wgOut->addHTML( $this->editFormTextBeforeContent );
+               $out->addHTML( $this->editFormTextBeforeContent );
                $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
-               $wgOut->addHTML( $this->editFormTextAfterContent );
+               $out->addHTML( $this->editFormTextAfterContent );
 
-               $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
+               $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
 
-               $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
+               $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
 
-               $wgOut->addHTML( $this->editFormTextBottom );
+               $out->addHTML( $this->editFormTextBottom );
                if ( $this->mTitle->exists() ) {
-                       $wgOut->returnToMain( null, $this->mTitle );
+                       $out->returnToMain( null, $this->mTitle );
                }
        }
 
@@ -850,7 +851,7 @@ class EditPage {
         * @throws ErrorPageError
         */
        public function importFormData( &$request ) {
-               global $wgContLang, $wgUser;
+               global $wgUser;
 
                # Section edit can come from either the form or a link
                $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
@@ -876,8 +877,7 @@ class EditPage {
                                }
                        }
 
-                       # Truncate for whole multibyte characters
-                       $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
+                       $this->summary = $request->getText( 'wpSummary' );
 
                        # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
                        # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
@@ -889,7 +889,7 @@ class EditPage {
                        # currently doing double duty as both edit summary and section title. Right now this
                        # is just to allow API edits to work around this limitation, but this should be
                        # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
-                       $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
+                       $this->sectiontitle = $request->getText( 'wpSectionTitle' );
                        $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
 
                        $this->edittime = $request->getVal( 'wpEdittime' );
@@ -1123,7 +1123,7 @@ class EditPage {
         * @since 1.21
         */
        protected function getContentObject( $def_content = null ) {
-               global $wgOut, $wgRequest, $wgUser, $wgContLang;
+               global $wgRequest, $wgUser, $wgContLang;
 
                $content = false;
 
@@ -1231,9 +1231,10 @@ class EditPage {
                                                $undoMsg = 'norev';
                                        }
 
+                                       $out = $this->context->getOutput();
                                        // Messages: undo-success, undo-failure, undo-norev, undo-nochange
                                        $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
-                                       $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
+                                       $this->editFormPageTop .= $out->parse( "<div class=\"{$class}\">" .
                                                $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
                                }
 
@@ -1460,7 +1461,7 @@ class EditPage {
                        $val = 'restored';
                }
 
-               $response = RequestContext::getMain()->getRequest()->response();
+               $response = $this->context->getRequest()->response();
                $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
        }
 
@@ -1506,7 +1507,7 @@ class EditPage {
         * @return bool False, if output is done, true if rest of the form should be displayed
         */
        private function handleStatus( Status $status, $resultDetails ) {
-               global $wgUser, $wgOut;
+               global $wgUser;
 
                /**
                 * @todo FIXME: once the interface for internalAttemptSave() is made
@@ -1523,9 +1524,11 @@ class EditPage {
                        }
                }
 
+               $out = $this->context->getOutput();
+
                // "wpExtraQueryRedirect" is a hidden input to modify
                // after save URL and is not used by actual edit form
-               $request = RequestContext::getMain()->getRequest();
+               $request = $this->context->getRequest();
                $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
 
                switch ( $status->value ) {
@@ -1546,7 +1549,7 @@ class EditPage {
 
                        case self::AS_CANNOT_USE_CUSTOM_MODEL:
                        case self::AS_PARSE_ERROR:
-                               $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
+                               $out->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
                                return true;
 
                        case self::AS_SUCCESS_NEW_ARTICLE:
@@ -1559,7 +1562,7 @@ class EditPage {
                                        }
                                }
                                $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
-                               $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
+                               $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
                                return false;
 
                        case self::AS_SUCCESS_UPDATE:
@@ -1587,7 +1590,7 @@ class EditPage {
                                        }
                                }
 
-                               $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
+                               $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
                                return false;
 
                        case self::AS_SPAM_ERROR:
@@ -2342,29 +2345,31 @@ class EditPage {
        }
 
        public function setHeaders() {
-               global $wgOut, $wgUser, $wgAjaxEditStash;
+               global $wgUser, $wgAjaxEditStash;
 
-               $wgOut->addModules( 'mediawiki.action.edit' );
-               $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
+               $out = $this->context->getOutput();
+
+               $out->addModules( 'mediawiki.action.edit' );
+               $out->addModuleStyles( 'mediawiki.action.edit.styles' );
 
                if ( $wgUser->getOption( 'showtoolbar' ) ) {
                        // The addition of default buttons is handled by getEditToolbar() which
                        // has its own dependency on this module. The call here ensures the module
                        // is loaded in time (it has position "top") for other modules to register
                        // buttons (e.g. extensions, gadgets, user scripts).
-                       $wgOut->addModules( 'mediawiki.toolbar' );
+                       $out->addModules( 'mediawiki.toolbar' );
                }
 
                if ( $wgUser->getOption( 'uselivepreview' ) ) {
-                       $wgOut->addModules( 'mediawiki.action.edit.preview' );
+                       $out->addModules( 'mediawiki.action.edit.preview' );
                }
 
                if ( $wgUser->getOption( 'useeditwarning' ) ) {
-                       $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
+                       $out->addModules( 'mediawiki.action.edit.editWarning' );
                }
 
                # Enabled article-related sidebar, toplinks, etc.
-               $wgOut->setArticleRelated( true );
+               $out->setArticleRelated( true );
 
                $contextTitle = $this->getContextTitle();
                if ( $this->isConflict ) {
@@ -2387,10 +2392,10 @@ class EditPage {
                if ( $displayTitle === false ) {
                        $displayTitle = $contextTitle->getPrefixedText();
                }
-               $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
+               $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
                # Transmit the name of the message to JavaScript for live preview
                # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
-               $wgOut->addJsConfigVars( [
+               $out->addJsConfigVars( [
                        'wgEditMessage' => $msg,
                        'wgAjaxEditStash' => $wgAjaxEditStash,
                ] );
@@ -2400,16 +2405,17 @@ class EditPage {
         * Show all applicable editing introductions
         */
        protected function showIntro() {
-               global $wgOut, $wgUser;
+               global $wgUser;
                if ( $this->suppressIntro ) {
                        return;
                }
 
+               $out = $this->context->getOutput();
                $namespace = $this->mTitle->getNamespace();
 
                if ( $namespace == NS_MEDIAWIKI ) {
                        # Show a warning if editing an interface message
-                       $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+                       $out->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
                        # If this is a default message (but not css or js),
                        # show a hint that it is translatable on translatewiki.net
                        if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
@@ -2417,7 +2423,7 @@ class EditPage {
                        ) {
                                $defaultMessageText = $this->mTitle->getDefaultMessageText();
                                if ( $defaultMessageText !== false ) {
-                                       $wgOut->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
+                                       $out->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
                                                'translateinterface' );
                                }
                        }
@@ -2429,11 +2435,11 @@ class EditPage {
                                # there must be a description url to show a hint to shared repo
                                if ( $descUrl ) {
                                        if ( !$this->mTitle->exists() ) {
-                                               $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
+                                               $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
                                                                        'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
                                                ] );
                                        } else {
-                                               $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
+                                               $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
                                                                        'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
                                                ] );
                                        }
@@ -2449,12 +2455,12 @@ class EditPage {
                        $ip = User::isIP( $username );
                        $block = Block::newFromTarget( $user, $user );
                        if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
-                               $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
+                               $out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
                                        [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
                        } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
                                # Show log extract if the user is currently blocked
                                LogEventsList::showLogExtract(
-                                       $wgOut,
+                                       $out,
                                        'block',
                                        MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
                                        '',
@@ -2475,7 +2481,7 @@ class EditPage {
                                $this->context->msg( 'helppage' )->inContentLanguage()->text()
                        ) );
                        if ( $wgUser->isLoggedIn() ) {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        // Suppress the external link icon, consider the help url an internal one
                                        "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
                                        [
@@ -2484,7 +2490,7 @@ class EditPage {
                                        ]
                                );
                        } else {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        // Suppress the external link icon, consider the help url an internal one
                                        "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
                                        [
@@ -2498,7 +2504,7 @@ class EditPage {
                if ( !$this->mTitle->exists() ) {
                        $dbr = wfGetDB( DB_REPLICA );
 
-                       LogEventsList::showLogExtract( $wgOut, [ 'delete', 'move' ], $this->mTitle,
+                       LogEventsList::showLogExtract( $out, [ 'delete', 'move' ], $this->mTitle,
                                '',
                                [
                                        'lim' => 10,
@@ -2519,9 +2525,8 @@ class EditPage {
                if ( $this->editintro ) {
                        $title = Title::newFromText( $this->editintro );
                        if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
-                               global $wgOut;
                                // Added using template syntax, to take <noinclude>'s into account.
-                               $wgOut->addWikiTextTitleTidy(
+                               $this->context->getOutput()->addWikiTextTitleTidy(
                                        '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
                                        $this->mTitle
                                );
@@ -2593,7 +2598,7 @@ class EditPage {
        }
 
        /**
-        * Send the edit form and related headers to $wgOut
+        * Send the edit form and related headers to OutputPage
         * @param callable|null $formCallback That takes an OutputPage parameter; will be called
         *     during form output near the top, for captchas and the like.
         *
@@ -2601,7 +2606,7 @@ class EditPage {
         * use the EditPage::showEditForm:fields hook instead.
         */
        public function showEditForm( $formCallback = null ) {
-               global $wgOut, $wgUser;
+               global $wgUser;
 
                # need to parse the preview early so that we know which templates are used,
                # otherwise users with "show preview after edit box" will get a blank list
@@ -2612,9 +2617,11 @@ class EditPage {
                        $previewOutput = $this->getPreviewText();
                }
 
+               $out = $this->context->getOutput();
+
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
-               Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$wgOut ] );
+               Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
 
                $this->setHeaders();
 
@@ -2627,19 +2634,19 @@ class EditPage {
                        // We use $this->section to much before this and getVal('wgSection') directly in other places
                        // at this point we can't reset $this->section to '' to fallback to non-section editing.
                        // Someone is welcome to try refactoring though
-                       $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
+                       $out->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
                        return;
                }
 
                $this->showHeader();
 
-               $wgOut->addHTML( $this->editFormPageTop );
+               $out->addHTML( $this->editFormPageTop );
 
                if ( $wgUser->getOption( 'previewontop' ) ) {
                        $this->displayPreviewArea( $previewOutput, true );
                }
 
-               $wgOut->addHTML( $this->editFormTextTop );
+               $out->addHTML( $this->editFormTextTop );
 
                $showToolbar = true;
                if ( $this->wasDeletedSinceLastEdit() ) {
@@ -2648,14 +2655,14 @@ class EditPage {
                                // Add an confirmation checkbox and explanation.
                                $showToolbar = false;
                        } else {
-                               $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
+                               $out->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
                                        'deletedwhileediting' );
                        }
                }
 
                // @todo add EditForm plugin interface and use it here!
                //       search for textarea1 and textarea2, and allow EditForm to override all uses.
-               $wgOut->addHTML( Html::openElement(
+               $out->addHTML( Html::openElement(
                        'form',
                        [
                                'class' => 'mw-editform',
@@ -2669,11 +2676,11 @@ class EditPage {
 
                if ( is_callable( $formCallback ) ) {
                        wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
-                       call_user_func_array( $formCallback, [ &$wgOut ] );
+                       call_user_func_array( $formCallback, [ &$out ] );
                }
 
                // Add an empty field to trip up spambots
-               $wgOut->addHTML(
+               $out->addHTML(
                        Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
                        . Html::rawElement(
                                'label',
@@ -2694,7 +2701,7 @@ class EditPage {
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
-               Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$wgOut ] );
+               Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
 
                // Put these up at the top to ensure they aren't lost on early form submission
                $this->showFormBeforeText();
@@ -2708,7 +2715,7 @@ class EditPage {
                        $key = $comment === ''
                                ? 'confirmrecreate-noreason'
                                : 'confirmrecreate';
-                       $wgOut->addHTML(
+                       $out->addHTML(
                                '<div class="mw-confirm-recreate">' .
                                        $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
                                Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
@@ -2720,7 +2727,7 @@ class EditPage {
 
                # When the summary is hidden, also hide them on preview/show changes
                if ( $this->nosummary ) {
-                       $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
+                       $out->addHTML( Html::hidden( 'nosummary', true ) );
                }
 
                # If a blank edit summary was previously provided, and the appropriate
@@ -2731,15 +2738,15 @@ class EditPage {
                # For a bit more sophisticated detection of blank summaries, hash the
                # automatic one and pass that in the hidden field wpAutoSummary.
                if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
-                       $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
+                       $out->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
                }
 
                if ( $this->undidRev ) {
-                       $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
+                       $out->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
                }
 
                if ( $this->selfRedirect ) {
-                       $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
+                       $out->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
                }
 
                if ( $this->hasPresetSummary ) {
@@ -2750,29 +2757,29 @@ class EditPage {
                }
 
                $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
-               $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
+               $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
 
-               $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
-               $wgOut->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
+               $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+               $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
 
-               $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
-               $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
+               $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
+               $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
 
-               $wgOut->enableOOUI();
+               $out->enableOOUI();
 
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
-                       $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
+                       $out->addHTML( $this->getSummaryPreview( true, $this->summary ) );
                }
 
-               $wgOut->addHTML( $this->editFormTextBeforeContent );
+               $out->addHTML( $this->editFormTextBeforeContent );
 
                if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
-                       $wgOut->addHTML( self::getEditToolbar( $this->mTitle ) );
+                       $out->addHTML( self::getEditToolbar( $this->mTitle ) );
                }
 
                if ( $this->blankArticle ) {
-                       $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
+                       $out->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
                }
 
                if ( $this->isConflict ) {
@@ -2790,7 +2797,7 @@ class EditPage {
                        $this->showContentForm();
                }
 
-               $wgOut->addHTML( $this->editFormTextAfterContent );
+               $out->addHTML( $this->editFormTextAfterContent );
 
                $this->showStandardInputs();
 
@@ -2800,17 +2807,17 @@ class EditPage {
 
                $this->showEditTools();
 
-               $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
+               $out->addHTML( $this->editFormTextAfterTools . "\n" );
 
-               $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
+               $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
 
-               $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
+               $out->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
                        Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
 
-               $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
+               $out->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
                        self::getPreviewLimitReport( $this->mParserOutput ) ) );
 
-               $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
+               $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
 
                if ( $this->isConflict ) {
                        try {
@@ -2823,7 +2830,7 @@ class EditPage {
                                        $this->contentFormat,
                                        $ex->getMessage()
                                );
-                               $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+                               $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
                        }
                }
 
@@ -2837,12 +2844,12 @@ class EditPage {
                } else {
                        $mode = 'text';
                }
-               $wgOut->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
+               $out->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
 
                // Marker for detecting truncated form data.  This must be the last
                // parameter sent in order to be of use, so do not move me.
-               $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) );
-               $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
+               $out->addHTML( Html::hidden( 'wpUltimateParam', true ) );
+               $out->addHTML( $this->editFormTextBottom . "\n</form>\n" );
 
                if ( !$wgUser->getOption( 'previewontop' ) ) {
                        $this->displayPreviewArea( $previewOutput, false );
@@ -2891,11 +2898,12 @@ class EditPage {
        }
 
        protected function showHeader() {
-               global $wgOut, $wgUser;
+               global $wgUser;
                global $wgAllowUserCss, $wgAllowUserJs;
 
+               $out = $this->context->getOutput();
                if ( $this->isConflict ) {
-                       $this->addExplainConflictHeader( $wgOut );
+                       $this->addExplainConflictHeader( $out );
                        $this->editRevId = $this->page->getLatest();
                } else {
                        if ( $this->section != '' && $this->section != 'new' ) {
@@ -2910,43 +2918,43 @@ class EditPage {
                        $buttonLabel = $this->context->msg( $this->getSaveButtonLabel() )->text();
 
                        if ( $this->missingComment ) {
-                               $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
+                               $out->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
                        }
 
                        if ( $this->missingSummary && $this->section != 'new' ) {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        "<div id='mw-missingsummary'>\n$1\n</div>",
                                        [ 'missingsummary', $buttonLabel ]
                                );
                        }
 
                        if ( $this->missingSummary && $this->section == 'new' ) {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        "<div id='mw-missingcommentheader'>\n$1\n</div>",
                                        [ 'missingcommentheader', $buttonLabel ]
                                );
                        }
 
                        if ( $this->blankArticle ) {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        "<div id='mw-blankarticle'>\n$1\n</div>",
                                        [ 'blankarticle', $buttonLabel ]
                                );
                        }
 
                        if ( $this->selfRedirect ) {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        "<div id='mw-selfredirect'>\n$1\n</div>",
                                        [ 'selfredirect', $buttonLabel ]
                                );
                        }
 
                        if ( $this->hookError !== '' ) {
-                               $wgOut->addWikiText( $this->hookError );
+                               $out->addWikiText( $this->hookError );
                        }
 
                        if ( !$this->checkUnicodeCompliantBrowser() ) {
-                               $wgOut->addWikiMsg( 'nonunicodebrowser' );
+                               $out->addWikiMsg( 'nonunicodebrowser' );
                        }
 
                        if ( $this->section != 'new' ) {
@@ -2955,12 +2963,12 @@ class EditPage {
                                        // Let sysop know that this will make private content public if saved
 
                                        if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
-                                               $wgOut->wrapWikiMsg(
+                                               $out->wrapWikiMsg(
                                                        "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
                                                        'rev-deleted-text-permission'
                                                );
                                        } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
-                                               $wgOut->wrapWikiMsg(
+                                               $out->wrapWikiMsg(
                                                        "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
                                                        'rev-deleted-text-view'
                                                );
@@ -2968,26 +2976,26 @@ class EditPage {
 
                                        if ( !$revision->isCurrent() ) {
                                                $this->mArticle->setOldSubtitle( $revision->getId() );
-                                               $wgOut->addWikiMsg( 'editingold' );
+                                               $out->addWikiMsg( 'editingold' );
                                                $this->isOldRev = true;
                                        }
                                } elseif ( $this->mTitle->exists() ) {
                                        // Something went wrong
 
-                                       $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
+                                       $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
                                                [ 'missing-revision', $this->oldid ] );
                                }
                        }
                }
 
                if ( wfReadOnly() ) {
-                       $wgOut->wrapWikiMsg(
+                       $out->wrapWikiMsg(
                                "<div id=\"mw-read-only-warning\">\n$1\n</div>",
                                [ 'readonlywarning', wfReadOnlyReason() ]
                        );
                } elseif ( $wgUser->isAnon() ) {
                        if ( $this->formtype != 'preview' ) {
-                               $wgOut->wrapWikiMsg(
+                               $out->wrapWikiMsg(
                                        "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
                                        [ 'anoneditwarning',
                                                // Log-in link
@@ -3001,7 +3009,7 @@ class EditPage {
                                        ]
                                );
                        } else {
-                               $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
+                               $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
                                        'anonpreviewwarning'
                                );
                        }
@@ -3009,25 +3017,25 @@ class EditPage {
                        if ( $this->isCssJsSubpage ) {
                                # Check the skin exists
                                if ( $this->isWrongCaseCssJsPage ) {
-                                       $wgOut->wrapWikiMsg(
+                                       $out->wrapWikiMsg(
                                                "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
                                                [ 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
                                        );
                                }
                                if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
-                                       $wgOut->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
+                                       $out->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
                                                $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'
                                        );
                                        if ( $this->formtype !== 'preview' ) {
                                                if ( $this->isCssSubpage && $wgAllowUserCss ) {
-                                                       $wgOut->wrapWikiMsg(
+                                                       $out->wrapWikiMsg(
                                                                "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
                                                                [ 'usercssyoucanpreview' ]
                                                        );
                                                }
 
                                                if ( $this->isJsSubpage && $wgAllowUserJs ) {
-                                                       $wgOut->wrapWikiMsg(
+                                                       $out->wrapWikiMsg(
                                                                "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
                                                                [ 'userjsyoucanpreview' ]
                                                        );
@@ -3163,8 +3171,6 @@ class EditPage {
         * @param string $summary The text of the summary to display
         */
        protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
-               global $wgOut;
-
                # Add a class if 'missingsummary' is triggered to allow styling of the summary line
                $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
                if ( $isSubjectPreview ) {
@@ -3178,7 +3184,7 @@ class EditPage {
                }
 
                $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
-               $wgOut->addHTML( $this->getSummaryInputWidget(
+               $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
                                $summary,
                                $labelText,
                                [ 'class' => $summaryClass ]
@@ -3215,21 +3221,20 @@ class EditPage {
        }
 
        protected function showFormBeforeText() {
-               global $wgOut;
-
-               $wgOut->addHTML( Html::hidden( 'wpSection', htmlspecialchars( $this->section ) ) );
-               $wgOut->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
-               $wgOut->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
-               $wgOut->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
-               $wgOut->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
+               $out = $this->context->getOutput();
+               $out->addHTML( Html::hidden( 'wpSection', htmlspecialchars( $this->section ) ) );
+               $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
+               $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
+               $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
+               $out->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
 
                if ( !$this->checkUnicodeCompliantBrowser() ) {
-                       $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
+                       $out->addHTML( Html::hidden( 'safemode', '1' ) );
                }
        }
 
        protected function showFormAfterText() {
-               global $wgOut, $wgUser;
+               global $wgUser;
                /**
                 * To make it harder for someone to slip a user a page
                 * which submits an edit form to the wiki without their
@@ -3242,7 +3247,9 @@ class EditPage {
                 * include the constant suffix to prevent editing from
                 * broken text-mangling proxies.
                 */
-               $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
+               $this->context->getOutput()->addHTML(
+                       "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n"
+               );
        }
 
        /**
@@ -3316,18 +3323,17 @@ class EditPage {
        }
 
        protected function showTextbox( $text, $name, $customAttribs = [] ) {
-               global $wgOut, $wgUser;
+               global $wgUser;
 
                $wikitext = $this->safeUnicodeOutput( $text );
                $wikitext = $this->addNewLineAtEnd( $wikitext );
 
                $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $wgUser );
 
-               $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
+               $this->context->getOutput()->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
        }
 
        protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
-               global $wgOut;
                $classes = [];
                if ( $isOnTop ) {
                        $classes[] = 'ontop';
@@ -3339,7 +3345,8 @@ class EditPage {
                        $attribs['style'] = 'display: none;';
                }
 
-               $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
+               $out = $this->context->getOutput();
+               $out->addHTML( Xml::openElement( 'div', $attribs ) );
 
                if ( $this->formtype == 'preview' ) {
                        $this->showPreview( $previewOutput );
@@ -3348,10 +3355,10 @@ class EditPage {
                        $pageViewLang = $this->mTitle->getPageViewLanguage();
                        $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
                                'class' => 'mw-content-' . $pageViewLang->getDir() ];
-                       $wgOut->addHTML( Html::rawElement( 'div', $attribs ) );
+                       $out->addHTML( Html::rawElement( 'div', $attribs ) );
                }
 
-               $wgOut->addHTML( '</div>' );
+               $out->addHTML( '</div>' );
 
                if ( $this->formtype == 'diff' ) {
                        try {
@@ -3363,26 +3370,26 @@ class EditPage {
                                        $this->contentFormat,
                                        $ex->getMessage()
                                );
-                               $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+                               $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
                        }
                }
        }
 
        /**
-        * Append preview output to $wgOut.
+        * Append preview output to OutputPage.
         * Includes category rendering if this is a category page.
         *
         * @param string $text The HTML to be output for the preview.
         */
        protected function showPreview( $text ) {
-               global $wgOut;
                if ( $this->mArticle instanceof CategoryPage ) {
                        $this->mArticle->openShowCategory();
                }
                # This hook seems slightly odd here, but makes things more
                # consistent for extensions.
-               Hooks::run( 'OutputPageBeforeHTML', [ &$wgOut, &$text ] );
-               $wgOut->addHTML( $text );
+               $out = $this->context->getOutput();
+               Hooks::run( 'OutputPageBeforeHTML', [ &$out, &$text ] );
+               $out->addHTML( $text );
                if ( $this->mArticle instanceof CategoryPage ) {
                        $this->mArticle->closeShowCategory();
                }
@@ -3396,7 +3403,7 @@ class EditPage {
         * save and then make a comparison.
         */
        public function showDiff() {
-               global $wgUser, $wgContLang, $wgOut;
+               global $wgUser, $wgContLang;
 
                $oldtitlemsg = 'currentrev';
                # if message does not exist, show diff against the preloaded default
@@ -3451,7 +3458,7 @@ class EditPage {
                        $difftext = '';
                }
 
-               $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
+               $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
        }
 
        /**
@@ -3460,8 +3467,7 @@ class EditPage {
        protected function showHeaderCopyrightWarning() {
                $msg = 'editpage-head-copy-warn';
                if ( !$this->context->msg( $msg )->isDisabled() ) {
-                       global $wgOut;
-                       $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
+                       $this->context->getOutput()->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
                                'editpage-head-copy-warn' );
                }
        }
@@ -3478,10 +3484,10 @@ class EditPage {
                $msg = 'editpage-tos-summary';
                Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
                if ( !$this->context->msg( $msg )->isDisabled() ) {
-                       global $wgOut;
-                       $wgOut->addHTML( '<div class="mw-tos-summary">' );
-                       $wgOut->addWikiMsg( $msg );
-                       $wgOut->addHTML( '</div>' );
+                       $out = $this->context->getOutput();
+                       $out->addHTML( '<div class="mw-tos-summary">' );
+                       $out->addWikiMsg( $msg );
+                       $out->addHTML( '</div>' );
                }
        }
 
@@ -3490,8 +3496,7 @@ class EditPage {
         * characters not present on most keyboards for copying/pasting.
         */
        protected function showEditTools() {
-               global $wgOut;
-               $wgOut->addHTML( '<div class="mw-editTools">' .
+               $this->context->getOutput()->addHTML( '<div class="mw-editTools">' .
                        $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
                        '</div>' );
        }
@@ -3585,28 +3590,28 @@ class EditPage {
        }
 
        protected function showStandardInputs( &$tabindex = 2 ) {
-               global $wgOut;
-               $wgOut->addHTML( "<div class='editOptions'>\n" );
+               $out = $this->context->getOutput();
+               $out->addHTML( "<div class='editOptions'>\n" );
 
                if ( $this->section != 'new' ) {
                        $this->showSummaryInput( false, $this->summary );
-                       $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
+                       $out->addHTML( $this->getSummaryPreview( false, $this->summary ) );
                }
 
-               $checkboxes = $this->getCheckboxesOOUI(
+               $checkboxes = $this->getCheckboxesWidget(
                        $tabindex,
                        [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
                );
                $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
 
-               $wgOut->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
+               $out->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
 
                // Show copyright warning.
-               $wgOut->addWikiText( $this->getCopywarn() );
-               $wgOut->addHTML( $this->editFormTextAfterWarn );
+               $out->addWikiText( $this->getCopywarn() );
+               $out->addHTML( $this->editFormTextAfterWarn );
 
-               $wgOut->addHTML( "<div class='editButtons'>\n" );
-               $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
+               $out->addHTML( "<div class='editButtons'>\n" );
+               $out->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
 
                $cancel = $this->getCancelLink();
                if ( $cancel !== '' ) {
@@ -3626,13 +3631,13 @@ class EditPage {
                        $this->context->msg( 'word-separator' )->escaped() .
                        $this->context->msg( 'newwindow' )->parse();
 
-               $wgOut->addHTML( "      <span class='cancelLink'>{$cancel}</span>\n" );
-               $wgOut->addHTML( "      <span class='editHelp'>{$edithelp}</span>\n" );
-               $wgOut->addHTML( "</div><!-- editButtons -->\n" );
+               $out->addHTML( "        <span class='cancelLink'>{$cancel}</span>\n" );
+               $out->addHTML( "        <span class='editHelp'>{$edithelp}</span>\n" );
+               $out->addHTML( "</div><!-- editButtons -->\n" );
 
-               Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $wgOut, &$tabindex ] );
+               Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
 
-               $wgOut->addHTML( "</div><!-- editOptions -->\n" );
+               $out->addHTML( "</div><!-- editOptions -->\n" );
        }
 
        /**
@@ -3640,14 +3645,13 @@ class EditPage {
         * If you want to use another entry point to this function, be careful.
         */
        protected function showConflict() {
-               global $wgOut;
-
+               $out = $this->context->getOutput();
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
-               if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$wgOut ] ) ) {
+               if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
                        $this->incrementConflictStats();
 
-                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
+                       $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
 
                        $content1 = $this->toEditContent( $this->textbox1 );
                        $content2 = $this->toEditContent( $this->textbox2 );
@@ -3660,7 +3664,7 @@ class EditPage {
                                $this->context->msg( 'storedversion' )->text()
                        );
 
-                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+                       $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                        $this->showTextbox2();
                }
        }
@@ -3790,9 +3794,11 @@ class EditPage {
         * @return string
         */
        public function getPreviewText() {
-               global $wgOut, $wgRawHtml, $wgLang;
+               global $wgRawHtml, $wgLang;
                global $wgAllowUserCss, $wgAllowUserJs;
 
+               $out = $this->context->getOutput();
+
                if ( $wgRawHtml && !$this->mTokenOk ) {
                        // Could be an offsite preview attempt. This is very unsafe if
                        // HTML is enabled, as it could be an attack.
@@ -3801,7 +3807,7 @@ class EditPage {
                                // Do not put big scary notice, if previewing the empty
                                // string, which happens when you initially edit
                                // a category page, due to automatic preview-on-open.
-                               $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
+                               $parsedNote = $out->parse( "<div class='previewnote'>" .
                                        $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
                                        true, /* interface */true );
                        }
@@ -3889,7 +3895,7 @@ class EditPage {
                        $parserOutput = $parserResult['parserOutput'];
                        $previewHTML = $parserResult['html'];
                        $this->mParserOutput = $parserOutput;
-                       $wgOut->addParserOutputMetadata( $parserOutput );
+                       $out->addParserOutputMetadata( $parserOutput );
 
                        if ( count( $parserOutput->getWarnings() ) ) {
                                $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
@@ -3915,7 +3921,7 @@ class EditPage {
 
                $previewhead = "<div class='previewnote'>\n" .
                        '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
-                       $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
+                       $out->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
 
                $pageViewLang = $this->mTitle->getPageViewLanguage();
                $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
@@ -4181,6 +4187,7 @@ class EditPage {
         * Returns an array of html code of the following checkboxes old style:
         * minor and watch
         *
+        * @deprecated since 1.30 Use getCheckboxesWidget() or getCheckboxesDefinition() instead
         * @param int &$tabindex Current tabindex
         * @param array $checked See getCheckboxesDefinition()
         * @return array
@@ -4236,21 +4243,34 @@ class EditPage {
        }
 
        /**
-        * Returns an array of html code of the following checkboxes:
-        * minor and watch
+        * Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and
+        * any other added by extensions.
         *
+        * @deprecated since 1.30 Use getCheckboxesWidget() or getCheckboxesDefinition() instead
         * @param int &$tabindex Current tabindex
         * @param array $checked Array of checkbox => bool, where bool indicates the checked
         *                 status of the checkbox
         *
-        * @return array
+        * @return array Associative array of string keys to OOUI\FieldLayout instances
         */
        public function getCheckboxesOOUI( &$tabindex, $checked ) {
+               return $this->getCheckboxesWidget( $tabindex, $checked );
+       }
+
+       /**
+        * Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and
+        * any other added by extensions.
+        *
+        * @param int &$tabindex Current tabindex
+        * @param array $checked Array of checkbox => bool, where bool indicates the checked
+        *                 status of the checkbox
+        *
+        * @return array Associative array of string keys to OOUI\FieldLayout instances
+        */
+       public function getCheckboxesWidget( &$tabindex, $checked ) {
                $checkboxes = [];
                $checkboxesDef = $this->getCheckboxesDefinition( $checked );
 
-               $origTabindex = $tabindex;
-
                foreach ( $checkboxesDef as $name => $options ) {
                        $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
 
@@ -4263,9 +4283,6 @@ class EditPage {
                        if ( isset( $options['title-message'] ) ) {
                                $title = $this->context->msg( $options['title-message'] )->text();
                        }
-                       if ( isset( $options['label-id'] ) ) {
-                               $labelAttribs['id'] = $options['label-id'];
-                       }
 
                        $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
                                new OOUI\CheckboxInputWidget( [
@@ -4289,7 +4306,19 @@ class EditPage {
                // Backwards-compatibility hack to run the EditPageBeforeEditChecks hook. It's important,
                // people have used it for the weirdest things completely unrelated to checkboxes...
                // And if we're gonna run it, might as well allow its legacy checkboxes to be shown.
-               $legacyCheckboxes = $this->getCheckboxes( $origTabindex, $checked );
+               $legacyCheckboxes = [];
+               if ( !$this->isNew ) {
+                       $legacyCheckboxes['minor'] = '';
+               }
+               $legacyCheckboxes['watch'] = '';
+               // Copy new-style checkboxes into an old-style structure
+               foreach ( $checkboxes as $name => $oouiLayout ) {
+                       $legacyCheckboxes[$name] = (string)$oouiLayout;
+               }
+               // Avoid PHP 7.1 warning of passing $this by reference
+               $ep = $this;
+               Hooks::run( 'EditPageBeforeEditChecks', [ &$ep, &$legacyCheckboxes, &$tabindex ], '1.29' );
+               // Copy back any additional old-style checkboxes into the new-style structure
                foreach ( $legacyCheckboxes as $name => $html ) {
                        if ( $html && !isset( $checkboxes[$name] ) ) {
                                $checkboxes[$name] = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $html ) ] );
@@ -4400,18 +4429,17 @@ class EditPage {
         * they have attempted to edit a nonexistent section.
         */
        public function noSuchSectionPage() {
-               global $wgOut;
-
-               $wgOut->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
+               $out = $this->context->getOutput();
+               $out->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
 
                $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
                Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
-               $wgOut->addHTML( $res );
+               $out->addHTML( $res );
 
-               $wgOut->returnToMain( false, $this->mTitle );
+               $out->returnToMain( false, $this->mTitle );
        }
 
        /**
@@ -4420,28 +4448,29 @@ class EditPage {
         * @param string|array|bool $match Text (or array of texts) which triggered one or more filters
         */
        public function spamPageWithContent( $match = false ) {
-               global $wgOut, $wgLang;
+               global $wgLang;
                $this->textbox2 = $this->textbox1;
 
                if ( is_array( $match ) ) {
                        $match = $wgLang->listToText( $match );
                }
-               $wgOut->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
+               $out = $this->context->getOutput();
+               $out->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
 
-               $wgOut->addHTML( '<div id="spamprotected">' );
-               $wgOut->addWikiMsg( 'spamprotectiontext' );
+               $out->addHTML( '<div id="spamprotected">' );
+               $out->addWikiMsg( 'spamprotectiontext' );
                if ( $match ) {
-                       $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
+                       $out->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
                }
-               $wgOut->addHTML( '</div>' );
+               $out->addHTML( '</div>' );
 
-               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
+               $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
                $this->showDiff();
 
-               $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+               $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                $this->showTextbox2();
 
-               $wgOut->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
+               $out->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
        }
 
        /**
@@ -4582,15 +4611,14 @@ class EditPage {
         * @since 1.29
         */
        protected function addEditNotices() {
-               global $wgOut;
-
+               $out = $this->context->getOutput();
                $editNotices = $this->mTitle->getEditNotices( $this->oldid );
                if ( count( $editNotices ) ) {
-                       $wgOut->addHTML( implode( "\n", $editNotices ) );
+                       $out->addHTML( implode( "\n", $editNotices ) );
                } else {
                        $msg = $this->context->msg( 'editnotice-notext' );
                        if ( !$msg->isDisabled() ) {
-                               $wgOut->addHTML(
+                               $out->addHTML(
                                        '<div class="mw-editnotice-notext">'
                                        . $msg->parseAsBlock()
                                        . '</div>'
@@ -4603,10 +4631,8 @@ class EditPage {
         * @since 1.29
         */
        protected function addTalkPageText() {
-               global $wgOut;
-
                if ( $this->mTitle->isTalkPage() ) {
-                       $wgOut->addWikiMsg( 'talkpagetext' );
+                       $this->context->getOutput()->addWikiMsg( 'talkpagetext' );
                }
        }
 
@@ -4614,14 +4640,15 @@ class EditPage {
         * @since 1.29
         */
        protected function addLongPageWarningHeader() {
-               global $wgMaxArticleSize, $wgOut, $wgLang;
+               global $wgMaxArticleSize, $wgLang;
 
                if ( $this->contentLength === false ) {
                        $this->contentLength = strlen( $this->textbox1 );
                }
 
+               $out = $this->context->getOutput();
                if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) {
-                       $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
+                       $out->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
                                [
                                        'longpageerror',
                                        $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ),
@@ -4630,7 +4657,7 @@ class EditPage {
                        );
                } else {
                        if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
-                               $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
+                               $out->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
                                        [
                                                'longpage-hint',
                                                $wgLang->formatSize( strlen( $this->textbox1 ) ),
@@ -4645,8 +4672,7 @@ class EditPage {
         * @since 1.29
         */
        protected function addPageProtectionWarningHeaders() {
-               global $wgOut;
-
+               $out = $this->context->getOutput();
                if ( $this->mTitle->isProtected( 'edit' ) &&
                        MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
                ) {
@@ -4657,7 +4683,7 @@ class EditPage {
                                # Then it must be protected based on static groups (regular)
                                $noticeMsg = 'protectedpagewarning';
                        }
-                       LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
+                       LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
                                [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
                }
                if ( $this->mTitle->isCascadeProtected() ) {
@@ -4673,10 +4699,10 @@ class EditPage {
                                }
                        }
                        $notice .= '</div>';
-                       $wgOut->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
+                       $out->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
                }
                if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
-                       LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
+                       LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
                                [ 'lim' => 1,
                                        'showIfEmpty' => false,
                                        'msgKey' => [ 'titleprotectedwarning' ],
index 39dc642..2f8255b 100644 (file)
@@ -442,7 +442,6 @@ class MovePage {
        private function moveToInternal( User $user, &$nt, $reason = '', $createRedirect = true,
                array $changeTags = []
        ) {
-               global $wgContLang;
                if ( $nt->exists() ) {
                        $moveOverRedirect = true;
                        $logType = 'move_redir';
@@ -520,8 +519,6 @@ class MovePage {
                if ( $reason ) {
                        $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
                }
-               # Truncate for whole multibyte characters.
-               $comment = $wgContLang->truncate( $comment, 255 );
 
                $dbw = wfGetDB( DB_MASTER );
 
index 99d15a7..006e700 100644 (file)
@@ -1401,7 +1401,7 @@ class Revision implements IDBAccessObject {
         *
         * @param IDatabase $dbw (master connection)
         * @throws MWException
-        * @return int
+        * @return int The revision ID
         */
        public function insertOn( $dbw ) {
                global $wgDefaultExternalStore, $wgContentHandlerUseDB;
@@ -1518,6 +1518,16 @@ class Revision implements IDBAccessObject {
                        );
                }
 
+               // Insert IP revision into ip_changes for use when querying for a range.
+               if ( $this->mUser === 0 && IP::isValid( $this->mUserText ) ) {
+                       $ipcRow = [
+                               'ipc_rev_id'        => $this->mId,
+                               'ipc_rev_timestamp' => $row['rev_timestamp'],
+                               'ipc_hex'           => IP::toHex( $row['rev_user_text'] ),
+                       ];
+                       $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
+               }
+
                // Avoid PHP 7.1 warning of passing $this by reference
                $revision = $this;
                Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, $flags ] );
@@ -1703,7 +1713,7 @@ class Revision implements IDBAccessObject {
         * @return Revision|null Revision or null on error
         */
        public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
-               global $wgContentHandlerUseDB, $wgContLang;
+               global $wgContentHandlerUseDB;
 
                $fields = [ 'page_latest', 'page_namespace', 'page_title',
                                                'rev_text_id', 'rev_len', 'rev_sha1' ];
@@ -1730,9 +1740,6 @@ class Revision implements IDBAccessObject {
                                $user = $wgUser;
                        }
 
-                       // Truncate for whole multibyte characters
-                       $summary = $wgContLang->truncate( $summary, 255 );
-
                        $row = [
                                'page'       => $pageId,
                                'user_text'  => $user->getName(),
index 0687a15..c97a42b 100644 (file)
@@ -1356,7 +1356,7 @@ class Title implements LinkTarget {
         * get the talk page, if it is a subject page get the talk page
         *
         * @since 1.25
-        * @throws MWException
+        * @throws MWException If the page doesn't have an other page
         * @return Title
         */
        public function getOtherPage() {
@@ -1366,6 +1366,9 @@ class Title implements LinkTarget {
                if ( $this->isTalkPage() ) {
                        return $this->getSubjectPage();
                } else {
+                       if ( !$this->canHaveTalkPage() ) {
+                               throw new MWException( "{$this->getPrefixedText()} does not have an other page" );
+                       }
                        return $this->getTalkPage();
                }
        }
index 021f426..7025477 100644 (file)
@@ -131,7 +131,7 @@ class CreditsAction extends FormlessAction {
                $anon_ips = [];
 
                # Sift for real versus user names
-               /** @var $user User */
+               /** @var User $user */
                foreach ( $contributors as $user ) {
                        $cnt--;
                        if ( $user->isLoggedIn() ) {
index 68dda37..d3ba0aa 100644 (file)
@@ -848,7 +848,7 @@ class InfoAction extends FormlessAction {
                $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
 
                # Sift for real versus user names
-               /** @var $user User */
+               /** @var User $user */
                foreach ( $contributors as $user ) {
                        $page = $user->isAnon()
                                ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
index 2245195..4360b4d 100644 (file)
@@ -62,7 +62,7 @@ class ApiEditPage extends ApiBase {
 
                                $redirValues = [];
 
-                               /** @var $newTitle Title */
+                               /** @var Title $newTitle */
                                foreach ( $titles as $id => $newTitle ) {
                                        if ( !isset( $titles[$id - 1] ) ) {
                                                $titles[$id - 1] = $oldTitle;
@@ -359,7 +359,7 @@ class ApiEditPage extends ApiBase {
                $articleContext->setWikiPage( $pageObj );
                $articleContext->setUser( $this->getUser() );
 
-               /** @var $articleObject Article */
+               /** @var Article $articleObject */
                $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
 
                $ep = new EditPage( $articleObject );
index e6f3fc4..987bb99 100644 (file)
@@ -245,7 +245,7 @@ class ApiQuery extends ApiBase {
                $cacheMode = $this->mPageSet->getCacheMode();
 
                // Execute all unfinished modules
-               /** @var $module ApiQueryBase */
+               /** @var ApiQueryBase $module */
                foreach ( $modules as $module ) {
                        $params = $module->extractRequestParams();
                        $cacheMode = $this->mergeCacheMode(
@@ -381,7 +381,7 @@ class ApiQuery extends ApiBase {
                        ];
                }
                // Report special pages
-               /** @var $title Title */
+               /** @var Title $title */
                foreach ( $pageSet->getSpecialTitles() as $fakeId => $title ) {
                        $vals = [];
                        ApiQueryBase::addTitleInfo( $vals, $title );
@@ -434,7 +434,7 @@ class ApiQuery extends ApiBase {
                $titles = $pageSet->getGoodTitles();
                if ( count( $titles ) ) {
                        $user = $this->getUser();
-                       /** @var $title Title */
+                       /** @var Title $title */
                        foreach ( $titles as $title ) {
                                if ( $title->userCan( 'read', $user ) ) {
                                        $exportTitles[] = $title;
index 56cbaac..54be254 100644 (file)
@@ -228,7 +228,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
                $titleWhere = [];
                $allRedirNs = [];
                $allRedirDBkey = [];
-               /** @var $t Title */
+               /** @var Title $t */
                foreach ( $this->redirTitles as $t ) {
                        $redirNs = $t->getNamespace();
                        $redirDBkey = $t->getDBkey();
index 2a3bf38..25e9b27 100644 (file)
@@ -47,7 +47,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
                $titles = $this->getPageSet()->getGoodAndMissingTitles();
                $cattitles = [];
                foreach ( $categories as $c ) {
-                       /** @var $t Title */
+                       /** @var Title $t */
                        $t = $titles[$c];
                        $cattitles[$c] = $t->getDBkey();
                }
index 2ebd6de..0eaeaec 100644 (file)
@@ -91,7 +91,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
 
                $sha1s = [];
                foreach ( $files as $file ) {
-                       /** @var $file File */
+                       /** @var File $file */
                        $sha1s[$file->getName()] = $file->getSha1();
                }
 
@@ -114,7 +114,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
                        if ( $params['dir'] == 'descending' ) {
                                $dupFiles = array_reverse( $dupFiles );
                        }
-                       /** @var $dupFile File */
+                       /** @var File $dupFile */
                        foreach ( $dupFiles as $dupFile ) {
                                $dupName = $dupFile->getName();
                                if ( $image == $dupName && $dupFile->isLocal() ) {
index 7b0080e..b1df982 100644 (file)
@@ -124,7 +124,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                        }
                                }
 
-                               /** @var $img File */
+                               /** @var File $img */
                                $img = $images[$title];
 
                                if ( self::getTransformCount() >= self::TRANSFORM_LIMIT ) {
@@ -199,7 +199,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                // Get one more to facilitate query-continue functionality
                                $count = ( $gotOne ? 1 : 0 );
                                $oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] );
-                               /** @var $oldie File */
+                               /** @var File $oldie */
                                foreach ( $oldies as $oldie ) {
                                        if ( ++$count > $params['limit'] ) {
                                                // We've reached the extra one which shows that there are
index ecdebd4..bff1978 100644 (file)
@@ -372,7 +372,7 @@ class ApiQueryInfo extends ApiQueryBase {
                        $this->getDisplayTitle();
                }
 
-               /** @var $title Title */
+               /** @var Title $title */
                foreach ( $this->everything as $pageid => $title ) {
                        $pageInfo = $this->extractPageInfo( $pageid, $title );
                        $fit = $pageInfo !== null && $result->addValue( [
@@ -548,7 +548,7 @@ class ApiQueryInfo extends ApiQueryBase {
 
                        $res = $this->select( __METHOD__ );
                        foreach ( $res as $row ) {
-                               /** @var $title Title */
+                               /** @var Title $title */
                                $title = $this->titles[$row->pr_page];
                                $a = [
                                        'type' => $row->pr_type,
@@ -688,7 +688,7 @@ class ApiQueryInfo extends ApiQueryBase {
        private function getTSIDs() {
                $getTitles = $this->talkids = $this->subjectids = [];
 
-               /** @var $t Title */
+               /** @var Title $t */
                foreach ( $this->everything as $t ) {
                        if ( MWNamespace::isTalk( $t->getNamespace() ) ) {
                                if ( $this->fld_subjectid ) {
index caa5f05..46c2265 100644 (file)
@@ -59,7 +59,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
                $params = $this->extractRequestParams();
                $result = $this->getResult();
 
-               /** @var $qp QueryPage */
+               /** @var QueryPage $qp */
                $qp = new $this->qpMap[$params['page']]();
                if ( !$qp->userCanExecute( $this->getUser() ) ) {
                        $this->dieWithError( 'apierror-specialpage-cantexecute' );
@@ -129,7 +129,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
        }
 
        public function getCacheMode( $params ) {
-               /** @var $qp QueryPage */
+               /** @var QueryPage $qp */
                $qp = new $this->qpMap[$params['page']]();
                if ( $qp->getRestriction() != '' ) {
                        return 'private';
index a4f0315..2dfa42a 100644 (file)
@@ -166,7 +166,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                        // For each page we will request, the user must have read rights for that page
                        $user = $this->getUser();
                        $status = Status::newGood();
-                       /** @var $title Title */
+                       /** @var Title $title */
                        foreach ( $pageSet->getGoodTitles() as $title ) {
                                if ( !$title->userCan( 'read', $user ) ) {
                                        $status->fatal( ApiMessage::create(
index 663416e..b6a0a78 100644 (file)
@@ -150,7 +150,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
                                );
 
                                // Now, put the valid titles into the result
-                               /** @var $title Title */
+                               /** @var Title $title */
                                foreach ( $pageSet->getTitles() as $title ) {
                                        $ns = $title->getNamespace();
                                        $dbkey = $title->getDBkey();
index a283b5a..cfe1968 100644 (file)
@@ -71,7 +71,7 @@ class ApiUpload extends ApiBase {
                $this->checkPermissions( $user );
 
                // Fetch the file (usually a no-op)
-               /** @var $status Status */
+               /** @var Status $status */
                $status = $this->mUpload->fetchFile();
                if ( !$status->isGood() ) {
                        $this->dieStatus( $status );
@@ -772,7 +772,7 @@ class ApiUpload extends ApiBase {
                        $this->mParams['text'] = $this->mParams['comment'];
                }
 
-               /** @var $file LocalFile */
+               /** @var LocalFile $file */
                $file = $this->mUpload->getLocalFile();
 
                // For preferences mode, we want to watch if 'watchdefault' is set,
@@ -829,7 +829,7 @@ class ApiUpload extends ApiBase {
                        $result['result'] = 'Poll';
                        $result['stage'] = 'queued';
                } else {
-                       /** @var $status Status */
+                       /** @var Status $status */
                        $status = $this->mUpload->performUpload( $this->mParams['comment'],
                                $this->mParams['text'], $watch, $this->getUser(), $this->mParams['tags'] );
 
index 1a0cad9..c873685 100644 (file)
        "api-help-param-upload": "Debe ser enviado como un ficheiro importado usando multipart/form-data.",
        "api-help-param-multi-separate": "Separe os valores con <kbd>|</kbd> ou [[Special:ApiHelp/main#main/datatypes|outros]].",
        "api-help-param-multi-max": "O número máximo de valores é {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} para os bots).",
+       "api-help-param-multi-max-simple": "O número máximo de valores é {{PLURAL:1$|1$}}.",
        "api-help-param-multi-all": "Para especificar tódolos valores use <kbd>$1</kbd>.",
        "api-help-param-default": "Por defecto: $1",
        "api-help-param-default-empty": "Por defecto: <span class=\"apihelp-empty\">(baleiro)</span>",
index c9eabbb..e637be3 100644 (file)
        "apihelp-setpagelanguage-extended-description-disabled": "ページ言語の変更はこのwikiでは許可されていません。\n\nこの操作を利用するには、<var>[[mw:Special:MyLanguage/Manual:$wgPageLanguageUseDB|$wgPageLanguageUseDB]]</var> を設定してください。",
        "apihelp-setpagelanguage-param-title": "言語を変更したいページのページ名。<var>$1pageid</var> とは同時に使用できません。",
        "apihelp-setpagelanguage-param-pageid": "言語を変更したいページのページID。<var>$1title</var> とは同時に使用できません。",
+       "apihelp-setpagelanguage-param-reason": "変更の理由。",
        "apihelp-stashedit-param-title": "編集されているページのページ名。",
        "apihelp-stashedit-param-section": "節番号です。先頭の節の場合は <kbd>0</kbd>、新しい節の場合は <kbd>new</kbd>を指定します。",
        "apihelp-stashedit-param-sectiontitle": "新しい節の名前です。",
index b09f570..0b9a57b 100644 (file)
        "api-help-param-upload": "Deve ser postado como um upload de arquivo usando multipart/form-data.",
        "api-help-param-multi-separate": "Valores separados com <kbd>|</kbd> ou [[Special:ApiHelp/main#main/datatypes|alternativas]].",
        "api-help-param-multi-max": "O número máximo de valores é {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} para bots).",
+       "api-help-param-multi-max-simple": "O número máximo de valores é {{PLURAL:$1|$1}}.",
        "api-help-param-multi-all": "Para especificar todos os valores, use <kbd>$1</kbd>.",
        "api-help-param-default": "Padrão: $1",
        "api-help-param-default-empty": "Padrão: <span class=\"apihelp-empty\">(vazio)</span>",
index 5d19961..6fa6907 100644 (file)
@@ -134,7 +134,8 @@ class CategoryMembershipChange {
                        ),
                        $this->pageTitle,
                        $this->getPreviousRevisionTimestamp(),
-                       $this->revision
+                       $this->revision,
+                       $type === self::CATEGORY_ADDITION
                );
        }
 
@@ -146,6 +147,7 @@ class CategoryMembershipChange {
         * @param Title $pageTitle Title of the page that is being added or removed
         * @param string $lastTimestamp Parent revision timestamp of this change in TS_MW format
         * @param Revision|null $revision
+        * @param bool $added true, if the category was added, false for removed
         *
         * @throws MWException
         */
@@ -156,7 +158,8 @@ class CategoryMembershipChange {
                $comment,
                Title $pageTitle,
                $lastTimestamp,
-               $revision
+               $revision,
+               $added
        ) {
                $deleted = $revision ? $revision->getVisibility() & Revision::SUPPRESSED_USER : 0;
                $newRevId = $revision ? $revision->getId() : 0;
@@ -197,7 +200,8 @@ class CategoryMembershipChange {
                                $lastTimestamp,
                                $bot,
                                $ip,
-                               $deleted
+                               $deleted,
+                               $added
                        ]
                );
                $rc->save();
index 21a811e..8e24efe 100644 (file)
@@ -515,7 +515,7 @@ class EnhancedChangesList extends ChangesList {
 
                $sinceLast = 0;
                $unvisitedOldid = null;
-               /** @var $rcObj RCCacheEntry */
+               /** @var RCCacheEntry $rcObj */
                foreach ( $block as $rcObj ) {
                        // Same logic as below inside main foreach
                        if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) {
@@ -537,7 +537,7 @@ class EnhancedChangesList extends ChangesList {
 
                # Total change link
                $links = [];
-               /** @var $block0 RecentChange */
+               /** @var RecentChange $block0 */
                $block0 = $block[0];
                $last = $block[count( $block ) - 1];
                if ( !$allLogs ) {
index cd11070..fd789a6 100644 (file)
@@ -284,7 +284,7 @@ class RecentChange {
         * @param bool $noudp
         */
        public function save( $noudp = false ) {
-               global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
+               global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker;
 
                $dbw = wfGetDB( DB_MASTER );
                if ( !is_array( $this->mExtra ) ) {
@@ -315,9 +315,6 @@ class RecentChange {
                # Trim spaces on user supplied text
                $this->mAttribs['rc_comment'] = trim( $this->mAttribs['rc_comment'] );
 
-               # Make sure summary is truncated (whole multibyte characters)
-               $this->mAttribs['rc_comment'] = $wgContLang->truncate( $this->mAttribs['rc_comment'], 255 );
-
                # Fixup database timestamps
                $this->mAttribs['rc_timestamp'] = $dbw->timestamp( $this->mAttribs['rc_timestamp'] );
 
@@ -364,8 +361,8 @@ class RecentChange {
 
                        // Never send an RC notification email about categorization changes
                        if (
-                               $this->mAttribs['rc_type'] != RC_CATEGORIZE &&
-                               Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] )
+                               Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] ) &&
+                               $this->mAttribs['rc_type'] != RC_CATEGORIZE
                        ) {
                                // @FIXME: This would be better as an extension hook
                                // Send emails or email jobs once this row is safely committed
@@ -853,6 +850,7 @@ class RecentChange {
         * @param bool $bot true, if the change was made by a bot
         * @param string $ip IP address of the user, if the change was made anonymously
         * @param int $deleted Indicates whether the change has been deleted
+        * @param bool $added true, if the category was added, false for removed
         *
         * @return RecentChange
         */
@@ -867,8 +865,17 @@ class RecentChange {
                $lastTimestamp,
                $bot,
                $ip = '',
-               $deleted = 0
+               $deleted = 0,
+               $added = null
        ) {
+               // Done in a backwards compatible way.
+               $params = [
+                       'hidden-cat' => WikiCategoryPage::factory( $categoryTitle )->isHidden()
+               ];
+               if ( $added !== null ) {
+                       $params['added'] = $added;
+               }
+
                $rc = new RecentChange;
                $rc->mTitle = $categoryTitle;
                $rc->mPerformer = $user;
@@ -897,9 +904,7 @@ class RecentChange {
                        'rc_logid' => 0,
                        'rc_log_type' => null,
                        'rc_log_action' => '',
-                       'rc_params' => serialize( [
-                               'hidden-cat' => WikiCategoryPage::factory( $categoryTitle )->isHidden()
-                       ] )
+                       'rc_params' => serialize( $params )
                ];
 
                $rc->mExtra = [
index fb46ab4..7410886 100644 (file)
@@ -32,9 +32,13 @@ class CollationFa extends IcuCollation {
 
        // Really hacky - replace with stuff from other blocks.
        private $override = [
-               "\xd8\xa7" => "\xd8\xa1",
+               // U+0627 ARABIC LETTER ALEF => U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE
+               "\xd8\xa7" => "\xd8\xa3",
+               // U+0648 ARABIC LETTER WAW => U+0649 ARABIC LETTER ALEF MAKSURA
                "\xd9\x88" => "\xd9\x89",
+               // U+0672 ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE => U+F3001 (private use area)
                "\xd9\xb2" => "\xF3\xB3\x80\x81",
+               // U+0673 ARABIC LETTER ALEF WITH WAVY HAMZA BELOW => U+F3002 (private use area)
                "\xd9\xb3" => "\xF3\xB3\x80\x82",
        ];
 
index 40069f3..e8e250b 100644 (file)
@@ -149,7 +149,7 @@ class DeferredUpdates {
                if ( $update instanceof MergeableUpdate ) {
                        $class = get_class( $update ); // fully-qualified class
                        if ( isset( $queue[$class] ) ) {
-                               /** @var $existingUpdate MergeableUpdate */
+                               /** @var MergeableUpdate $existingUpdate */
                                $existingUpdate = $queue[$class];
                                $existingUpdate->merge( $update );
                        } else {
index 7f9af60..34f2852 100644 (file)
@@ -918,7 +918,7 @@ class DifferenceEngine extends ContextSource {
                        $wikidiff2Version = phpversion( 'wikidiff2' );
                        if (
                                $wikidiff2Version !== false &&
-                               version_compare( $wikidiff2Version, '0.3.0', '>=' )
+                               version_compare( $wikidiff2Version, '0.3', '>=' )
                        ) {
                                $text = wikidiff2_do_diff(
                                        $otext,
index bf232e9..a2ec391 100644 (file)
@@ -441,6 +441,24 @@ TXT;
                return "[$id] $url   $type from line $line of $file: $message";
        }
 
+       /**
+        * Get a normalised message for formatting with PSR-3 log event context.
+        *
+        * Must be used together with `getLogContext()` to be useful.
+        *
+        * @since 1.30
+        * @param Exception|Throwable $e
+        * @return string
+        */
+       public static function getLogNormalMessage( $e ) {
+               $type = get_class( $e );
+               $file = $e->getFile();
+               $line = $e->getLine();
+               $message = $e->getMessage();
+
+               return "[{exception_id}] {exception_url}   $type from line $line of $file: $message";
+       }
+
        /**
         * @param Exception|Throwable $e
         * @return string
@@ -468,6 +486,7 @@ TXT;
                return [
                        'exception' => $e,
                        'exception_id' => WebRequest::getRequestId(),
+                       'exception_url' => self::getURL() ?: '[no req]',
                        'caught_by' => $catcher
                ];
        }
@@ -595,7 +614,7 @@ TXT;
                if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
                        $logger = LoggerFactory::getInstance( 'exception' );
                        $logger->error(
-                               self::getLogMessage( $e ),
+                               self::getLogNormalMessage( $e ),
                                self::getLogContext( $e, $catcher )
                        );
 
@@ -629,7 +648,7 @@ TXT;
                        $logger = LoggerFactory::getInstance( $channel );
                        $logger->log(
                                $level,
-                               self::getLogMessage( $e ),
+                               self::getLogNormalMessage( $e ),
                                self::getLogContext( $e, $catcher )
                        );
                }
index 20d51c2..ed00793 100644 (file)
@@ -274,14 +274,13 @@ class LocalRepo extends FileRepo {
                        );
                };
 
-               $that = $this;
                $applyMatchingFiles = function ( ResultWrapper $res, &$searchSet, &$finalFiles )
-                       use ( $that, $fileMatchesSearch, $flags )
+                       use ( $fileMatchesSearch, $flags )
                {
                        global $wgContLang;
-                       $info = $that->getInfo();
+                       $info = $this->getInfo();
                        foreach ( $res as $row ) {
-                               $file = $that->newFileFromRow( $row );
+                               $file = $this->newFileFromRow( $row );
                                // There must have been a search for this DB key, but this has to handle the
                                // cases were title capitalization is different on the client and repo wikis.
                                $dbKeysLook = [ strtr( $file->getName(), ' ', '_' ) ];
index 460fe51..32f4504 100644 (file)
@@ -1147,7 +1147,7 @@ abstract class File implements IDBAccessObject {
                if ( !$thumb ) { // bad params?
                        $thumb = false;
                } elseif ( $thumb->isError() ) { // transform error
-                       /** @var $thumb MediaTransformError */
+                       /** @var MediaTransformError $thumb */
                        $this->lastError = $thumb->toText();
                        // Ignore errors if requested
                        if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
@@ -1282,11 +1282,10 @@ abstract class File implements IDBAccessObject {
                // Thumbnailing a very large file could result in network saturation if
                // everyone does it at once.
                if ( $this->getSize() >= 1e7 ) { // 10MB
-                       $that = $this;
                        $work = new PoolCounterWorkViaCallback( 'GetLocalFileCopy', sha1( $this->getName() ),
                                [
-                                       'doWork' => function () use ( $that ) {
-                                               return $that->getLocalRefPath();
+                                       'doWork' => function () {
+                                               return $this->getLocalRefPath();
                                        }
                                ]
                        );
index 904c932..810f788 100644 (file)
@@ -1195,8 +1195,6 @@ class LocalFile extends File {
        function upload( $src, $comment, $pageText, $flags = 0, $props = false,
                $timestamp = false, $user = null, $tags = []
        ) {
-               global $wgContLang;
-
                if ( $this->getRepo()->getReadOnlyReason() !== false ) {
                        return $this->readOnlyFatalStatus();
                }
@@ -1230,9 +1228,6 @@ class LocalFile extends File {
                // Trim spaces on user supplied text
                $comment = trim( $comment );
 
-               // Truncate nicely or the DB will do it for us
-               // non-nicely (dangling multi-byte chars, non-truncated version in cache).
-               $comment = $wgContLang->truncate( $comment, 255 );
                $this->lock(); // begin
                $status = $this->publish( $src, $flags, $options );
 
@@ -1556,7 +1551,7 @@ class LocalFile extends File {
                                                );
 
                                                if ( isset( $status->value['revision'] ) ) {
-                                                       /** @var $rev Revision */
+                                                       /** @var Revision $rev */
                                                        $rev = $status->value['revision'];
                                                        // Associate new page revision id
                                                        $logEntry->setAssociatedRevId( $rev->getId() );
@@ -1564,7 +1559,7 @@ class LocalFile extends File {
                                                // This relies on the resetArticleID() call in WikiPage::insertOn(),
                                                // which is triggered on $descTitle by doEditContent() above.
                                                if ( isset( $status->value['revision'] ) ) {
-                                                       /** @var $rev Revision */
+                                                       /** @var Revision $rev */
                                                        $rev = $status->value['revision'];
                                                        $updateLogPage = $rev->getPage();
                                                }
index 645fa8a..752bc54 100644 (file)
@@ -83,7 +83,8 @@ abstract class DatabaseUpdater {
                FixDefaultJsonContentPages::class,
                CleanupEmptyCategories::class,
                AddRFCAndPMIDInterwiki::class,
-               PopulatePPSortKey::class
+               PopulatePPSortKey::class,
+               PopulateIpChanges::class,
        ];
 
        /**
index 43d3574..d01f954 100644 (file)
@@ -266,7 +266,7 @@ class MssqlInstaller extends DatabaseInstaller {
                if ( !$status->isOK() ) {
                        return false;
                }
-               /** @var $conn Database */
+               /** @var Database $conn */
                $conn = $status->value;
 
                // We need the server-level ALTER ANY LOGIN permission to create new accounts
index 0250b6f..dc63899 100644 (file)
@@ -275,7 +275,7 @@ class MysqlInstaller extends DatabaseInstaller {
                if ( !$status->isOK() ) {
                        return false;
                }
-               /** @var $conn Database */
+               /** @var Database $conn */
                $conn = $status->value;
 
                // Get current account name
index b42ae46..2abc6b6 100644 (file)
@@ -266,7 +266,6 @@ class MysqlUpdater extends DatabaseUpdater {
                                'patch-fa_major_mime-chemical.sql' ],
 
                        // 1.25
-                       [ 'doUserNewTalkUseridUnsigned' ],
                        // note this patch covers other _comment and _description fields too
                        [ 'modifyField', 'recentchanges', 'rc_comment', 'patch-editsummary-length.sql' ],
 
@@ -327,6 +326,9 @@ class MysqlUpdater extends DatabaseUpdater {
                                'patch-user_properties-fix-pk.sql' ],
                        [ 'addTable', 'comment', 'patch-comment-table.sql' ],
                        [ 'migrateComments' ],
+                       [ 'renameIndex', 'l10n_cache', 'lc_lang_key', 'PRIMARY', false,
+                               'patch-l10n_cache-primary-key.sql' ],
+                       [ 'doUnsignedSyncronisation' ],
                ];
        }
 
@@ -1121,26 +1123,42 @@ class MysqlUpdater extends DatabaseUpdater {
                );
        }
 
-       protected function doUserNewTalkUseridUnsigned() {
-               if ( !$this->doTable( 'user_newtalk' ) ) {
-                       return true;
-               }
+       protected function doUnsignedSyncronisation() {
+               $sync = [
+                       [ 'table' => 'bot_passwords', 'field' => 'bp_user' ],
+                       [ 'table' => 'change_tag', 'field' => 'ct_log_id' ],
+                       [ 'table' => 'change_tag', 'field' => 'ct_rev_id' ],
+                       [ 'table' => 'page_restrictions', 'field' => 'pr_user' ],
+                       [ 'table' => 'tag_summary', 'field' => 'ts_log_id' ],
+                       [ 'table' => 'tag_summary', 'field' => 'ts_rev_id' ],
+                       [ 'table' => 'user_newtalk', 'field' => 'user_id' ],
+                       [ 'table' => 'user_properties', 'field' => 'up_user' ],
+               ];
 
-               $info = $this->db->fieldInfo( 'user_newtalk', 'user_id' );
-               if ( $info === false ) {
-                       return true;
-               }
-               if ( $info->isUnsigned() ) {
-                       $this->output( "...user_id is already unsigned int.\n" );
+               foreach ( $sync as $s ) {
+                       if ( !$this->doTable( $s['table'] ) ) {
+                               continue;
+                       }
 
-                       return true;
+                       $info = $this->db->fieldInfo( $s['table'], $s['field'] );
+                       if ( $info === false ) {
+                               continue;
+                       }
+                       $fullName = "{$s['table']}.{$s['field']}";
+                       if ( $info->isUnsigned() ) {
+                               $this->output( "...$fullName is already unsigned int.\n" );
+
+                               continue;
+                       }
+
+                       $this->applyPatch(
+                               "patch-{$s['table']}-{$s['field']}-unsigned.sql",
+                               false,
+                               "Making $fullName into an unsigned int"
+                       );
                }
 
-               return $this->applyPatch(
-                       'patch-user-newtalk-userid-unsigned.sql',
-                       false,
-                       'Making user_id unsigned int'
-               );
+               return true;
        }
 
        protected function doRevisionPageRevIndexNonUnique() {
index b501cb3..1a3fb10 100644 (file)
@@ -594,7 +594,7 @@ class PostgresInstaller extends DatabaseInstaller {
                        return $status;
                }
 
-               /** @var $conn DatabasePostgres */
+               /** @var DatabasePostgres $conn */
                $conn = $status->value;
 
                if ( $conn->tableExists( 'archive' ) ) {
index d0ed822..9f71001 100644 (file)
@@ -191,6 +191,8 @@ class SqliteUpdater extends DatabaseUpdater {
                                'patch-user_properties-fix-pk.sql' ],
                        [ 'addTable', 'comment', 'patch-comment-table.sql' ],
                        [ 'migrateComments' ],
+                       [ 'renameIndex', 'l10n_cache', 'lc_lang_key', 'PRIMARY', false,
+                               'patch-l10n_cache-primary-key.sql' ],
                ];
        }
 
index 5ae3a0c..7a49051 100644 (file)
@@ -98,6 +98,7 @@
        "config-no-cli-uploads-check": "<strong>Atenção:</strong> O seu diretório padrão para envios (<code>$1</code>) não está marcado para vulnerabilidade\npara execução de script arbitrário durante a instalação do CLI.",
        "config-brokenlibxml": "O sistema tem uma combinação de PHP e libxml2 que é conflitante e pode causar corrupção de dados ocultos no MediaWiki e outros aplicativos da web.\nAtualize para o libxml2 2.7.3 ou mais recente ([https://bugs.php.net/bug.php?id=45996 bugs com o PHP]).\nInstalação abortada.",
        "config-suhosin-max-value-length": "O Suhosin está instalado e limita o parâmetro GET <code>length</code> para $1 bytes. O componente ResourceLoader trabalhará em torno deste limite, mas degradará a performance.\nSe possível, defina <code>suhosin.get.max_value_length</code> em <code>php.ini</code> para 1024 ou mais e defina <code>$wgResourceLoaderMaxQueryLength</code> em <code>LocalSettings.php</code> para o mesmo valor.",
+       "config-using-32bit": "<strong>Aviso:</strong> o seu sistema parece estar sendo executado com inteiros de 32 bits. Isto [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit não é recomendado].",
        "config-db-type": "Tipo do banco de dados:",
        "config-db-host": "Servidor do banco de dados:",
        "config-db-host-help": "Se a banco de dados do seu servidor está em um servidor diferente, digite o nome do host ou o endereço IP aqui.\n\nSe você está utilizando uma hospedagem web compartilhada, o seu provedor de hospedagem deverá fornecer o nome do host correto na sua documentação.\n\nSe você está instalando em um servidor Windows e usando o MySQL, usar \"localhost\" pode não funcionar para o nome de servidor. Se não funcionar, tente \"127.0.0.1\" para o endereço de IP local.\n\nSe você está usando PostgreSQl, deixe este campo em branco para se conectar através de um socket Unix.",
        "config-help-tooltip": "clique para expandir",
        "config-nofile": "O arquivo \"$1\" não foi encontrado. Ele foi apagado?",
        "config-extension-link": "Você sabia que sua wiki suporta [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions extensões]?\n\nVocê pode explorar as [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category extensões por categoria] ou visitar a [https://www.mediawiki.org/wiki/Extension_Matrix Matriz de Extensões] para ver a lista completa.",
+       "config-skins-screenshots": "$1 (screenshots: $2)",
+       "config-screenshot": "screenshot",
        "mainpagetext": "<strong>O MediaWiki foi instalado.</strong>",
        "mainpagedocfooter": "Consulte o [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Manual de Usuário] para informações de como usar o software wiki.\n\n== Começando ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de opções de configuração]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ FAQ do MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traduza o MediaWiki para seu idioma]"
 }
index 0a7f8cd..1c441cd 100644 (file)
@@ -82,6 +82,7 @@
        "config-no-cli-uploads-check": "<strong>Cảnh báo:</strong> Thư mục tải lên mặc định của bạn (<code>$1</code>) không được kiểm tra lỗ hỏng bảo mật dễ bị khai thác bởi các đoạn mã thực thi xấu trong quá trình cài đặt giao diện dòng lệnh.",
        "config-brokenlibxml": "Hệ thống của bạn có kết hợp các phiên bản lỗi lầm của PHP và libxml2, điều này có thể gây ra tổn thương không nhìn thấy được đối với dữ liệu trong MediaWiki và các ứng dụng Web khác.\nHãy nâng cấp lên phiên bản libxml2 2.7.3 trở lên ([https://bugs.php.net/bug.php?id=45996 lỗi nộp PHP]).\nCài đặt bị hủy bỏ.",
        "config-suhosin-max-value-length": "Suhosin được cài đặt và hạn chế tham số GET <code>length</code> (độ dài) không thể vượt quá $1 byte.\nThành phần ResourceLoader của MediaWiki sẽ khắc phục giới hạn này, nhưng điều này sẽ làm giảm hiệu suất.\nNếu có thể, bạn nên tăng <code>suhosin.get.max_value_length</code> lên 1024 trở lên trong <code>php.ini</code>, và đặt <code>$wgResourceLoaderMaxQueryLength</code> cùng giá trị trong <code>LocalSettings.php</code>.",
+       "config-using-32bit": "<strong>Cảnh báo:</strong> Máy của bạn hình như sử dụng các số nguyên 32 bit. Chế độ này [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:32-bit không được khuyên khích].",
        "config-db-type": "Kiểu cơ sở dữ liệu:",
        "config-db-host": "Máy chủ của cơ sở dữ liệu:",
        "config-db-host-help": "Nếu máy chủ cơ sở dữ liệu của bạn nằm trên máy chủ khác, hãy điền tên hoặc địa chỉ IP của máy chủ vào đây.\n\nNếu bạn đang dùng Web hosting chia sẻ, tài liệu của nhà cung cấp hosting của bạn sẽ có tên chính xác của máy chủ.\n\nNếu bạn đang cài đặt trên một máy chủ Windows và sử dụng MySQL, việc dùng “localhost” có thể không hợp với tên máy chủ. Nếu bị như vậy, hãy thử “127.0.0.1” tức địa chỉ IP địa phương.\n\nNếu bạn đang dùng PostgreSQL, hãy để trống mục này để kết nối với một ổ cắm Unix.",
        "config-help-tooltip": "nhấn chuột để mở rộng",
        "config-nofile": "Không tìm thấy tập tin “$1”. Nó có phải bị xóa không?",
        "config-extension-link": "Bạn có biết rằng wiki của bạn có hỗ trợ [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions mở rộng]?\n\nBạn có thể truy cập [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category phần mở rộng theo thể loại] hoặc [https://www.mediawiki.org/wiki/Extension_Matrix Ma trận Mở rộng] để xem danh sách đầy đủ các phần mở rộng.",
+       "config-skins-screenshots": "$1 (ảnh chụp màn hình: $2)",
+       "config-screenshot": "ảnh chụp màn hình",
        "mainpagetext": "'''MediaWiki đã được cài đặt.'''",
        "mainpagedocfooter": "Xin đọc [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Hướng dẫn sử dụng] để biết thêm thông tin về cách sử dụng phần mềm wiki.\n\n== Để bắt đầu ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Danh sách các thiết lập cấu hình]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Các câu hỏi thường gặp MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Danh sách gửi thư về việc phát hành MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Tìm hiểu cách chống spam tại wiki của bạn]"
 }
index eb72edc..de5a103 100644 (file)
@@ -1250,7 +1250,7 @@ class SwiftFileBackend extends FileBackendStore {
         * @return StatusValue[]
         */
        protected function doExecuteOpHandlesInternal( array $fileOpHandles ) {
-               /** @var $statuses StatusValue[] */
+               /** @var StatusValue[] $statuses */
                $statuses = [];
 
                $auth = $this->getAuthentication();
index f002d3e..8121654 100644 (file)
@@ -315,7 +315,7 @@ class ChronologyProtector implements LoggerAwareInterface {
         * @return array
         */
        private static function mergePositions( $curValue, array $shutdownPositions ) {
-               /** @var $curPositions DBMasterPos[] */
+               /** @var DBMasterPos[] $curPositions */
                if ( $curValue === false ) {
                        $curPositions = $shutdownPositions;
                } else {
index b925e2c..4c3cbdd 100644 (file)
@@ -34,7 +34,7 @@ use IP;
  * @see Database
  */
 class DatabaseMysqli extends DatabaseMysqlBase {
-       /** @var $mConn mysqli */
+       /** @var mysqli $mConn */
 
        /**
         * @param string $sql
index 493cde8..12e59b5 100644 (file)
@@ -9,7 +9,7 @@ use stdClass;
  * doesn't go anywhere near an actual database.
  */
 class FakeResultWrapper extends ResultWrapper {
-       /** @var $result stdClass[] */
+       /** @var stdClass[] $result */
 
        /**
         * @param stdClass[] $rows
index 4cfa542..f6d080e 100644 (file)
@@ -239,7 +239,7 @@ interface ILBFactory {
         *   - cluster : wait on the given external load balancer DBs
         *   - timeout : Max wait time. Default: ~60 seconds
         *   - ifWritesSince: Only wait if writes were done since this UNIX timestamp
-        * @throws DBReplicationWaitError If a timeout or error occured waiting on a DB cluster
+        * @throws DBReplicationWaitError If a timeout or error occurred waiting on a DB cluster
         */
        public function waitForReplication( array $opts = [] );
 
index 36de39e..8393e2b 100644 (file)
@@ -384,9 +384,9 @@ class LoadBalancer implements ILoadBalancer {
                        throw new InvalidArgumentException( "Empty server array given to LoadBalancer" );
                }
 
-               /** @var $i int|bool Index of selected server */
+               /** @var int|bool $i Index of selected server */
                $i = false;
-               /** @var $laggedReplicaMode bool Whether server is considered lagged */
+               /** @var bool $laggedReplicaMode Whether server is considered lagged */
                $laggedReplicaMode = false;
 
                // Quickly look through the available servers for a server that meets criteria...
@@ -538,7 +538,7 @@ class LoadBalancer implements ILoadBalancer {
        public function getAnyOpenConnection( $i ) {
                foreach ( $this->mConns as $connsByServer ) {
                        if ( !empty( $connsByServer[$i] ) ) {
-                               /** @var $serverConns IDatabase[] */
+                               /** @var IDatabase[] $serverConns */
                                $serverConns = $connsByServer[$i];
 
                                return reset( $serverConns );
index 1da9468..6494c26 100644 (file)
@@ -56,7 +56,7 @@ class SamplingStatsdClient extends StatsdClient {
                }
                if ( $samplingRates ) {
                        array_walk( $data, function ( $item ) use ( $samplingRates ) {
-                               /** @var $item StatsdData */
+                               /** @var StatsdData $item */
                                foreach ( $samplingRates as $pattern => $rate ) {
                                        if ( fnmatch( $pattern, $item->getKey(), FNM_NOESCAPE ) ) {
                                                $item->setSampleRate( $item->getSampleRate() * $rate );
index 35c45de..90865ff 100644 (file)
@@ -61,11 +61,9 @@ class RestbaseVirtualRESTService extends VirtualRESTService {
                        'fixedUrl' => false,
                ], $params );
                // Ensure that the url parameter has a trailing slash.
-               $mparams['url'] = preg_replace(
-                       '#/?$#',
-                       '/',
-                       $mparams['url']
-               );
+               if ( substr( $mparams['url'], -1 ) !== '/' ) {
+                       $mparams['url'] .= '/';
+               }
                // Ensure the correct domain format: strip protocol, port,
                // and trailing slash if present.  This lets us use
                // $wgCanonicalServer as a default value, which is very convenient.
index 6197d40..8b51932 100644 (file)
@@ -593,8 +593,6 @@ class ManualLogEntry extends LogEntryBase {
         * @throws MWException
         */
        public function insert( IDatabase $dbw = null ) {
-               global $wgContLang;
-
                $dbw = $dbw ?: wfGetDB( DB_MASTER );
 
                if ( $this->timestamp === null ) {
@@ -604,9 +602,6 @@ class ManualLogEntry extends LogEntryBase {
                // Trim spaces on user supplied text
                $comment = trim( $this->getComment() );
 
-               // Truncate for whole multibyte characters.
-               $comment = $wgContLang->truncate( $comment, 255 );
-
                $params = $this->getParameters();
                $relations = $this->relations;
 
index 3b200fa..e421209 100644 (file)
@@ -327,8 +327,6 @@ class LogPage {
         * @return int The log_id of the inserted log entry
         */
        public function addEntry( $action, $target, $comment, $params = [], $doer = null ) {
-               global $wgContLang;
-
                if ( !is_array( $params ) ) {
                        $params = [ $params ];
                }
@@ -340,9 +338,6 @@ class LogPage {
                # Trim spaces on user supplied text
                $comment = trim( $comment );
 
-               # Truncate for whole multibyte characters.
-               $comment = $wgContLang->truncate( $comment, 255 );
-
                $this->action = $action;
                $this->target = $target;
                $this->comment = $comment;
index 32208cc..de438da 100644 (file)
@@ -221,7 +221,7 @@ abstract class TransformationalImageHandler extends ImageHandler {
                }
 
                # Try a hook. Called "Bitmap" for historical reasons.
-               /** @var $mto MediaTransformOutput */
+               /** @var MediaTransformOutput $mto */
                $mto = null;
                Hooks::run( 'BitmapHandlerTransform', [ $this, $image, &$scalerParams, &$mto ] );
                if ( !is_null( $mto ) ) {
index f6580e9..c98d4f7 100644 (file)
@@ -735,6 +735,17 @@ class PageArchive {
                                        ] );
 
                                $revision->insertOn( $dbw );
+
+                               // Also restore reference to the revision in ip_changes if it was an IP edit.
+                               if ( (int)$row->ar_rev_id === 0 && IP::isValid( $row->ar_user_text ) ) {
+                                       $ipcRow = [
+                                               'ipc_rev_id' => $row->ar_rev_id,
+                                               'ipc_rev_timestamp' => $row->ar_timestamp,
+                                               'ipc_hex' => IP::toHex( $row->ar_user_text ),
+                                       ];
+                                       $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
+                               }
+
                                $restored++;
 
                                Hooks::run( 'ArticleRevisionUndeleted',
index 5f6e455..bf8a597 100644 (file)
@@ -878,11 +878,10 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // Update the DB post-send if the page has not cached since now
-               $that = $this;
                $latest = $this->getLatest();
                DeferredUpdates::addCallableUpdate(
-                       function () use ( $that, $retval, $latest ) {
-                               $that->insertRedirectEntry( $retval, $latest );
+                       function () use ( $retval, $latest ) {
+                               $this->insertRedirectEntry( $retval, $latest );
                        },
                        DeferredUpdates::POSTSEND,
                        wfGetDB( DB_MASTER )
@@ -1658,7 +1657,7 @@ class WikiPage implements Page, IDBAccessObject {
                // Convenience variables
                $now = wfTimestampNow();
                $oldid = $meta['oldId'];
-               /** @var $oldContent Content|null */
+               /** @var Content|null $oldContent */
                $oldContent = $meta['oldContent'];
                $newsize = $content->getSize();
 
@@ -2298,7 +2297,7 @@ class WikiPage implements Page, IDBAccessObject {
        public function doUpdateRestrictions( array $limit, array $expiry,
                &$cascade, $reason, User $user, $tags = null
        ) {
-               global $wgCascadingRestrictionLevels, $wgContLang;
+               global $wgCascadingRestrictionLevels;
 
                if ( wfReadOnly() ) {
                        return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
@@ -2371,9 +2370,6 @@ class WikiPage implements Page, IDBAccessObject {
                        $logAction = 'protect';
                }
 
-               // Truncate for whole multibyte characters
-               $reason = $wgContLang->truncate( $reason, 255 );
-
                $logRelationsValues = [];
                $logRelationsField = null;
                $logParamsDetails = [];
@@ -2837,9 +2833,14 @@ class WikiPage implements Page, IDBAccessObject {
                        'FOR UPDATE',
                        $commentQuery['joins']
                );
+
                // Build their equivalent archive rows
                $rowsInsert = [];
                $revids = [];
+
+               /** @var int[] Revision IDs of edits that were made by IPs */
+               $ipRevIds = [];
+
                foreach ( $res as $row ) {
                        $comment = $revCommentStore->getComment( $row );
                        $rowInsert = [
@@ -2865,6 +2866,12 @@ class WikiPage implements Page, IDBAccessObject {
                        }
                        $rowsInsert[] = $rowInsert;
                        $revids[] = $row->rev_id;
+
+                       // Keep track of IP edits, so that the corresponding rows can
+                       // be deleted in the ip_changes table.
+                       if ( (int)$row->rev_user === 0 && IP::isValid( $row->rev_user_text ) ) {
+                               $ipRevIds[] = $row->rev_id;
+                       }
                }
                // Copy them into the archive table
                $dbw->insert( 'archive', $rowsInsert, __METHOD__ );
@@ -2883,6 +2890,11 @@ class WikiPage implements Page, IDBAccessObject {
                        $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
                }
 
+               // Also delete records from ip_changes as applicable.
+               if ( count( $ipRevIds ) > 0 ) {
+                       $dbw->delete( 'ip_changes', [ 'ipc_rev_id' => $ipRevIds ], __METHOD__ );
+               }
+
                // Log the deletion, if the page was suppressed, put it in the suppression log instead
                $logtype = $suppress ? 'suppress' : 'delete';
 
@@ -3146,9 +3158,6 @@ class WikiPage implements Page, IDBAccessObject {
                // Trim spaces on user supplied text
                $summary = trim( $summary );
 
-               // Truncate for whole multibyte characters.
-               $summary = $wgContLang->truncate( $summary, 255 );
-
                // Save
                $flags = EDIT_UPDATE | EDIT_INTERNAL;
 
index 48a9f94..afe900d 100644 (file)
@@ -52,7 +52,7 @@ abstract class FormattedRCFeed extends RCFeed {
         */
        public function notify( RecentChange $rc, $actionComment = null ) {
                $params = $this->params;
-               /** @var $formatter RCFeedFormatter */
+               /** @var RCFeedFormatter $formatter */
                $formatter = is_object( $params['formatter'] ) ? $params['formatter'] : new $params['formatter'];
 
                $line = $formatter->getLine( $params, $rc, $actionComment );
index 8973fe3..d535ffc 100644 (file)
@@ -296,6 +296,16 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                return true;
        }
 
+       /**
+        * @return array
+        */
+       public function getPreloadLinks( ResourceLoaderContext $context ) {
+               $url = self::getStartupModulesUrl( $context );
+               return [
+                       $url => [ 'as' => 'script' ]
+               ];
+       }
+
        /**
         * Base modules required for the base environment of ResourceLoader
         *
@@ -359,6 +369,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                }, [
                        '$VARS.wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
                        '$VARS.configuration' => $this->getConfigSettings( $context ),
+                       // This url may be preloaded. See getPreloadLinks().
                        '$VARS.baseModulesUri' => self::getStartupModulesUrl( $context ),
                ] );
                $pairs['$CODE.registrations()'] = str_replace(
index 32d4891..b098422 100644 (file)
@@ -23,8 +23,8 @@
  * Item class for a filearchive table row
  */
 class RevDelArchivedFileItem extends RevDelFileItem {
-       /** @var $list RevDelArchivedFileList */
-       /** @var $file ArchivedFile */
+       /** @var RevDelArchivedFileList $list */
+       /** @var ArchivedFile $file */
        /** @var LocalFile */
        protected $lockFile;
 
index a2c58e6..011c7b0 100644 (file)
@@ -83,7 +83,7 @@ abstract class RevDelList extends RevisionListBase {
        public function areAnySuppressed() {
                $bit = $this->getSuppressBit();
 
-               /** @var $item RevDelItem */
+               /** @var RevDelItem $item */
                foreach ( $this as $item ) {
                        if ( $item->getBits() & $bit ) {
                                return true;
@@ -151,7 +151,7 @@ abstract class RevDelList extends RevisionListBase {
                // passed to doPostCommitUpdates().
                $visibilityChangeMap = [];
 
-               /** @var $item RevDelItem */
+               /** @var RevDelItem $item */
                foreach ( $this as $item ) {
                        unset( $missing[$item->getId()] );
 
@@ -294,7 +294,7 @@ abstract class RevDelList extends RevisionListBase {
 
        final protected function acquireItemLocks() {
                $status = Status::newGood();
-               /** @var $item RevDelItem */
+               /** @var RevDelItem $item */
                foreach ( $this as $item ) {
                        $status->merge( $item->lock() );
                }
@@ -304,7 +304,7 @@ abstract class RevDelList extends RevisionListBase {
 
        final protected function releaseItemLocks() {
                $status = Status::newGood();
-               /** @var $item RevDelItem */
+               /** @var RevDelItem $item */
                foreach ( $this as $item ) {
                        $status->merge( $item->unlock() );
                }
index e1d0034..df7a9ed 100644 (file)
@@ -1251,11 +1251,10 @@ abstract class Skin extends ContextSource {
        function buildSidebar() {
                global $wgEnableSidebarCache, $wgSidebarCacheExpiry;
 
-               $that = $this;
-               $callback = function () use ( $that ) {
+               $callback = function () {
                        $bar = [];
-                       $that->addToSidebar( $bar, 'sidebar' );
-                       Hooks::run( 'SkinBuildSidebar', [ $that, &$bar ] );
+                       $this->addToSidebar( $bar, 'sidebar' );
+                       Hooks::run( 'SkinBuildSidebar', [ $this, &$bar ] );
 
                        return $bar;
                };
index 500c2e9..0cdc55f 100644 (file)
@@ -688,7 +688,7 @@ abstract class AuthManagerSpecialPage extends SpecialPage {
 
                        if ( isset( $singleFieldInfo['options'] ) ) {
                                $descriptor['options'] = array_flip( array_map( function ( $message ) {
-                                       /** @var $message Message */
+                                       /** @var Message $message */
                                        return $message->parse();
                                }, $singleFieldInfo['options'] ) );
                        }
index acd5d14..04d03f5 100644 (file)
@@ -519,16 +519,25 @@ abstract class ChangesListSpecialPage extends SpecialPage {
        public function execute( $subpage ) {
                $this->rcSubpage = $subpage;
 
-               $this->setHeaders();
-               $this->outputHeader();
-               $this->addModules();
-
                $rows = $this->getRows();
                $opts = $this->getOptions();
                if ( $rows === false ) {
                        $rows = new FakeResultWrapper( [] );
                }
 
+               // Used by Structured UI app to get results without MW chrome
+               if ( $this->getRequest()->getVal( 'action' ) === 'render' ) {
+                       $this->getOutput()->setArticleBodyOnly( true );
+               }
+
+               // Used by "live update" and "view newest" to check
+               // if there's new changes with minimal data transfer
+               if ( $this->getRequest()->getBool( 'peek' ) ) {
+                       $code = $rows->numRows() > 0 ? 200 : 304;
+                       $this->getOutput()->setStatusCode( $code );
+                       return;
+               }
+
                $batch = new LinkBatch;
                foreach ( $rows as $row ) {
                        $batch->add( NS_USER, $row->rc_user_text );
@@ -542,6 +551,10 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                        }
                }
                $batch->execute();
+
+               $this->setHeaders();
+               $this->outputHeader();
+               $this->addModules();
                $this->webOutput( $rows, $opts );
 
                $rows->free();
@@ -1536,7 +1549,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         *
         * @return bool
         */
-       protected function isStructuredFilterUiEnabled() {
+       public function isStructuredFilterUiEnabled() {
                return $this->getUser()->getOption( 'rcenhancedfilters' );
        }
 
index 66e4fbe..252dc68 100644 (file)
@@ -618,7 +618,7 @@ class SpecialBlock extends FormSpecialPage {
         * @return bool|string
         */
        public static function processForm( array $data, IContextSource $context ) {
-               global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang;
+               global $wgBlockAllowsUTEdit, $wgHideUserContribLimit;
 
                $performer = $context->getUser();
 
@@ -720,8 +720,7 @@ class SpecialBlock extends FormSpecialPage {
                $block = new Block();
                $block->setTarget( $target );
                $block->setBlocker( $performer );
-               # Truncate reason for whole multibyte characters
-               $block->mReason = $wgContLang->truncate( $data['Reason'][0], 255 );
+               $block->mReason = $data['Reason'][0];
                $block->mExpiry = $expiryTime;
                $block->prevents( 'createaccount', $data['CreateAccount'] );
                $block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) );
index bee6a39..87c899f 100644 (file)
@@ -154,8 +154,6 @@ class SpecialChangeContentModel extends FormSpecialPage {
        }
 
        public function onSubmit( array $data ) {
-               global $wgContLang;
-
                if ( $data['pagetitle'] === '' ) {
                        // Initial form view of special page, pass
                        return false;
@@ -240,8 +238,6 @@ class SpecialChangeContentModel extends FormSpecialPage {
                if ( $data['reason'] !== '' ) {
                        $reason .= $this->msg( 'colon-separator' )->inContentLanguage()->text() . $data['reason'];
                }
-               # Truncate for whole multibyte characters.
-               $reason = $wgContLang->truncate( $reason, 255 );
 
                // Run edit filters
                $derivativeContext = new DerivativeContext( $this->getContext() );
index 1b14fcb..5a5f005 100644 (file)
@@ -103,7 +103,12 @@ class SpecialContributions extends IncludableSpecialPage {
                                'pagetitle',
                                $this->msg( 'contributions-title', $target )->plain()
                        )->inContentLanguage() );
-                       $this->getSkin()->setRelevantUser( $userObj );
+
+                       # For IP ranges, we want the contributionsSub, but not the skin-dependent
+                       # links under 'Tools', which may include irrelevant links like 'Logs'.
+                       if ( !IP::isValidRange( $target ) ) {
+                               $this->getSkin()->setRelevantUser( $userObj );
+                       }
                } else {
                        $out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
                        $out->setHTMLTitle( $this->msg(
@@ -206,7 +211,12 @@ class SpecialContributions extends IncludableSpecialPage {
                                'associated' => $this->opts['associated'],
                        ] );
 
-                       if ( !$pager->getNumRows() ) {
+                       if ( IP::isValidRange( $target ) && !$pager->isQueryableRange( $target ) ) {
+                               // Valid range, but outside CIDR limit.
+                               $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
+                               $limit = $limits[ IP::isIPv4( $target ) ? 'IPv4' : 'IPv6' ];
+                               $out->addWikiMsg( 'sp-contributions-outofrange', $limit );
+                       } elseif ( !$pager->getNumRows() ) {
                                $out->addWikiMsg( 'nocontribs', $target );
                        } else {
                                # Show a message about replica DB lag, if applicable
@@ -223,11 +233,14 @@ class SpecialContributions extends IncludableSpecialPage {
                                }
                                $out->addHTML( $output );
                        }
+
                        $out->preventClickjacking( $pager->getPreventClickjacking() );
 
                        # Show the appropriate "footer" message - WHOIS tools, etc.
                        if ( $this->opts['contribs'] == 'newbie' ) {
                                $message = 'sp-contributions-footer-newbies';
+                       } elseif ( IP::isValidRange( $target ) ) {
+                               $message = 'sp-contributions-footer-anon-range';
                        } elseif ( IP::isIPAddress( $target ) ) {
                                $message = 'sp-contributions-footer-anon';
                        } elseif ( $userObj->isAnon() ) {
@@ -258,8 +271,11 @@ class SpecialContributions extends IncludableSpecialPage {
         */
        protected function contributionsSub( $userObj ) {
                if ( $userObj->isAnon() ) {
-                       // Show a warning message that the user being searched for doesn't exists
-                       if ( !User::isIP( $userObj->getName() ) ) {
+                       // Show a warning message that the user being searched for doesn't exists.
+                       // User::isIP returns true for IP address and usemod IPs like '123.123.123.xxx',
+                       // but returns false for IP ranges. We don't want to suggest either of these are
+                       // valid usernames which we would with the 'contributions-userdoesnotexist' message.
+                       if ( !User::isIP( $userObj->getName() ) && !$userObj->isIPRange() ) {
                                $this->getOutput()->wrapWikiMsg(
                                        "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
                                        [
@@ -286,7 +302,13 @@ class SpecialContributions extends IncludableSpecialPage {
                        // Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
                        // and also this will display a totally irrelevant log entry as a current block.
                        if ( !$this->including() ) {
-                               $block = Block::newFromTarget( $userObj, $userObj );
+                               // For IP ranges you must give Block::newFromTarget the CIDR string and not a user object.
+                               if ( $userObj->isIPRange() ) {
+                                       $block = Block::newFromTarget( $userObj->getName(), $userObj->getName() );
+                               } else {
+                                       $block = Block::newFromTarget( $userObj, $userObj );
+                               }
+
                                if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
                                        if ( $block->getType() == Block::TYPE_RANGE ) {
                                                $nt = MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget();
@@ -332,10 +354,14 @@ class SpecialContributions extends IncludableSpecialPage {
                $talkpage = $target->getTalkPage();
 
                $linkRenderer = $sp->getLinkRenderer();
-               $tools['user-talk'] = $linkRenderer->makeLink(
-                       $talkpage,
-                       $sp->msg( 'sp-contributions-talk' )->text()
-               );
+
+               # No talk pages for IP ranges.
+               if ( !IP::isValidRange( $username ) ) {
+                       $tools['user-talk'] = $linkRenderer->makeLink(
+                               $talkpage,
+                               $sp->msg( 'sp-contributions-talk' )->text()
+                       );
+               }
 
                if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
                        if ( $sp->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
@@ -374,24 +400,28 @@ class SpecialContributions extends IncludableSpecialPage {
                                );
                        }
                }
-               # Uploads
-               $tools['uploads'] = $linkRenderer->makeKnownLink(
-                       SpecialPage::getTitleFor( 'Listfiles', $username ),
-                       $sp->msg( 'sp-contributions-uploads' )->text()
-               );
 
-               # Other logs link
-               $tools['logs'] = $linkRenderer->makeKnownLink(
-                       SpecialPage::getTitleFor( 'Log', $username ),
-                       $sp->msg( 'sp-contributions-logs' )->text()
-               );
+               # Don't show some links for IP ranges
+               if ( !IP::isValidRange( $username ) ) {
+                       # Uploads
+                       $tools['uploads'] = $linkRenderer->makeKnownLink(
+                               SpecialPage::getTitleFor( 'Listfiles', $username ),
+                               $sp->msg( 'sp-contributions-uploads' )->text()
+                       );
 
-               # Add link to deleted user contributions for priviledged users
-               if ( $sp->getUser()->isAllowed( 'deletedhistory' ) ) {
-                       $tools['deletedcontribs'] = $linkRenderer->makeKnownLink(
-                               SpecialPage::getTitleFor( 'DeletedContributions', $username ),
-                               $sp->msg( 'sp-contributions-deleted', $username )->text()
+                       # Other logs link
+                       $tools['logs'] = $linkRenderer->makeKnownLink(
+                               SpecialPage::getTitleFor( 'Log', $username ),
+                               $sp->msg( 'sp-contributions-logs' )->text()
                        );
+
+                       # Add link to deleted user contributions for priviledged users
+                       if ( $sp->getUser()->isAllowed( 'deletedhistory' ) ) {
+                               $tools['deletedcontribs'] = $linkRenderer->makeKnownLink(
+                                       SpecialPage::getTitleFor( 'DeletedContributions', $username ),
+                                       $sp->msg( 'sp-contributions-deleted', $username )->text()
+                               );
+                       }
                }
 
                # Add a link to change user rights for privileged users
index 0a653e7..693b8aa 100644 (file)
@@ -155,7 +155,6 @@ class SpecialNewFiles extends IncludableSpecialPage {
 
                        'mediatype' => [
                                'type' => 'multiselect',
-                               'dropdown' => true,
                                'flatlist' => true,
                                'name' => 'mediatype',
                                'label-message' => 'newimages-mediatype',
index 4cdc78f..d0eb8e3 100644 (file)
@@ -1256,7 +1256,6 @@ class UploadForm extends HTMLForm {
                $out->addJsConfigVars( $scriptVars );
 
                $out->addModules( [
-                       'mediawiki.action.edit', // For <charinsert> support
                        'mediawiki.special.upload', // Extras for thumbnail and license preview.
                ] );
        }
index ba3cb87..7049744 100644 (file)
@@ -111,7 +111,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                }
        }
 
-       protected function isStructuredFilterUiEnabled() {
+       public function isStructuredFilterUiEnabled() {
                return parent::isStructuredFilterUiEnabled()
                        && ( $this->getConfig()->get( 'StructuredChangeFiltersOnWatchlist' )
                                || $this->getRequest()->getBool( 'rcfilters' ) );
index 2206be8..924fd06 100644 (file)
@@ -79,7 +79,7 @@ class BlockListPager extends TablePager {
                        }
                }
 
-               /** @var $row object */
+               /** @var object $row */
                $row = $this->mCurrentRow;
 
                $language = $this->getLanguage();
index d7819c4..31ea810 100644 (file)
@@ -87,6 +87,10 @@ class ContribsPager extends RangeChronologicalPager {
                }
                $this->getDateRangeCond( $startTimestamp, $endTimestamp );
 
+               // This property on IndexPager is set by $this->getIndexField() in parent::__construct().
+               // We need to reassign it here so that it is used when the actual query is ran.
+               $this->mIndexField = $this->getIndexField();
+
                // Most of this code will use the 'contributions' group DB, which can map to replica DBs
                // with extra user based indexes or partioning by user. The additional metadata
                // queries should use a regular replica DB since the lookup pattern is not all by user.
@@ -207,6 +211,12 @@ class ContribsPager extends RangeChronologicalPager {
                        'join_conds' => $join_cond
                ];
 
+               // For IPv6, we use ipc_rev_timestamp on ip_changes as the index field,
+               // which will be referenced when parsing the results of a query.
+               if ( self::isQueryableRange( $this->target ) ) {
+                       $queryInfo['fields'][] = 'ipc_rev_timestamp';
+               }
+
                ChangeTags::modifyDisplayQuery(
                        $queryInfo['tables'],
                        $queryInfo['fields'],
@@ -257,8 +267,18 @@ class ContribsPager extends RangeChronologicalPager {
                                $condition['rev_user'] = $uid;
                                $index = 'user_timestamp';
                        } else {
-                               $condition['rev_user_text'] = $this->target;
-                               $index = 'usertext_timestamp';
+                               $ipRangeConds = $this->getIpRangeConds( $this->mDb, $this->target );
+
+                               if ( $ipRangeConds ) {
+                                       $tables[] = 'ip_changes';
+                                       $join_conds['ip_changes'] = [
+                                               'LEFT JOIN', [ 'ipc_rev_id = rev_id' ]
+                                       ];
+                                       $condition[] = $ipRangeConds;
+                               } else {
+                                       $condition['rev_user_text'] = $this->target;
+                                       $index = 'usertext_timestamp';
+                               }
                        }
                }
 
@@ -305,8 +325,57 @@ class ContribsPager extends RangeChronologicalPager {
                return [];
        }
 
-       function getIndexField() {
-               return 'rev_timestamp';
+       /**
+        * Get SQL conditions for an IP range, if applicable
+        * @param IDatabase      $db
+        * @param string         $ip The IP address or CIDR
+        * @return string|false  SQL for valid IP ranges, false if invalid
+        */
+       private function getIpRangeConds( $db, $ip ) {
+               // First make sure it is a valid range and they are not outside the CIDR limit
+               if ( !$this->isQueryableRange( $ip ) ) {
+                       return false;
+               }
+
+               list( $start, $end ) = IP::parseRange( $ip );
+
+               return 'ipc_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end );
+       }
+
+       /**
+        * Is the given IP a range and within the CIDR limit?
+        *
+        * @param string $ipRange
+        * @return bool True if it is valid
+        * @since 1.30
+        */
+       public function isQueryableRange( $ipRange ) {
+               $limits = $this->getConfig()->get( 'RangeContributionsCIDRLimit' );
+
+               $bits = IP::parseCIDR( $ipRange )[1];
+               if (
+                       ( $bits === false ) ||
+                       ( IP::isIPv4( $ipRange ) && $bits < $limits['IPv4'] ) ||
+                       ( IP::isIPv6( $ipRange ) && $bits < $limits['IPv6'] )
+               ) {
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Override of getIndexField() in IndexPager.
+        * For IP ranges, it's faster to use the replicated ipc_rev_timestamp
+        * on the `ip_changes` table than the rev_timestamp on the `revision` table.
+        * @return string Name of field
+        */
+       public function getIndexField() {
+               if ( $this->isQueryableRange( $this->target ) ) {
+                       return 'ipc_rev_timestamp';
+               } else {
+                       return 'rev_timestamp';
+               }
        }
 
        function doBatchLookups() {
@@ -325,6 +394,9 @@ class ContribsPager extends RangeChronologicalPager {
                                if ( $this->contribs === 'newbie' ) { // multiple users
                                        $batch->add( NS_USER, $row->user_name );
                                        $batch->add( NS_USER_TALK, $row->user_name );
+                               } elseif ( $this->isQueryableRange( $this->target ) ) {
+                                       // If this is an IP range, batch the IP's talk page
+                                       $batch->add( NS_USER_TALK, $row->rev_user_text );
                                }
                                $batch->add( $row->page_namespace, $row->page_title );
                        }
@@ -400,6 +472,7 @@ class ContribsPager extends RangeChronologicalPager {
                        # Mark current revisions
                        $topmarktext = '';
                        $user = $this->getUser();
+
                        if ( $row->rev_id === $row->page_latest ) {
                                $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
                                $classes[] = 'mw-contributions-current';
@@ -473,8 +546,10 @@ class ContribsPager extends RangeChronologicalPager {
 
                        # Show user names for /newbies as there may be different users.
                        # Note that only unprivileged users have rows with hidden user names excluded.
+                       # When querying for an IP range, we want to always show user and user talk links.
                        $userlink = '';
-                       if ( $this->contribs == 'newbie' && !$rev->isDeleted( Revision::DELETED_USER ) ) {
+                       if ( ( $this->contribs == 'newbie' && !$rev->isDeleted( Revision::DELETED_USER ) )
+                               || $this->isQueryableRange( $this->target ) ) {
                                $userlink = ' . . ' . $lang->getDirMark()
                                        . Linker::userLink( $rev->getUser(), $rev->getUserText() );
                                $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
index 20b44d2..1587abc 100644 (file)
@@ -120,7 +120,7 @@ class ProtectedPagesPager extends TablePager {
         * @throws MWException
         */
        function formatValue( $field, $value ) {
-               /** @var $row object */
+               /** @var object $row */
                $row = $this->mCurrentRow;
 
                switch ( $field ) {
index dd8b975..efc0fd4 100644 (file)
@@ -328,7 +328,6 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
                                                        # Disallow Talk:File:x type titles...
                                                        throw new MalformedTitleException( 'title-invalid-talk-namespace', $text );
                                                } elseif ( $this->interwikiLookup->isValidInterwiki( $x[1] ) ) {
-                                                       // TODO: get rid of global state!
                                                        # Disallow Talk:Interwiki:x type titles...
                                                        throw new MalformedTitleException( 'title-invalid-talk-namespace', $text );
                                                }
index 8506846..0c39610 100644 (file)
@@ -827,6 +827,16 @@ class User implements IDBAccessObject {
                        || IP::isIPv6( $name );
        }
 
+       /**
+        * Is the user an IP range?
+        *
+        * @since 1.30
+        * @return bool
+        */
+       public function isIPRange() {
+               return IP::isValidRange( $this->mName );
+       }
+
        /**
         * Is the input a valid username?
         *
index c3662c9..b2b0f63 100644 (file)
@@ -7,6 +7,7 @@
                        "Tokoabibi"
                ]
        },
+       "underline-never": "caaytu",
        "sunday": "pilipayan",
        "monday": "sakacacay a demied nu lipay",
        "tuesday": "sakatusa a demied nu lipay",
@@ -80,7 +81,7 @@
        "category-article-count": "{{PLURAL:$2|uyni kakuniza hatiza ku cacay yamalyilu sailuc-kakuniza. kina kakuniza yamalyilu isasa $2 a sailuc-kasasizuma, ilabu {{PLURAL:$1}}mahiza ku isasaay}}",
        "category-file-count": "{{PLURAL:$2|kakuniza yamalyilu isasaay a cacay ku tangan. kakuniza yamalyilu isasaay izaway $1 ku tangan, pulung $2 makalaan.}}",
        "listingcontinuesabbrev": "palalid",
-       "about": "mahizaay u",
+       "about": "mahizaay",
        "newwindow": "(paynin baluhay a azih-sasingalan miwawah)",
        "cancel": "palawpes",
        "moredotdotdot": "yadah...",
        "anontalk": "sasukamu",
        "navigation": "pasubana’ tu miidangay",
        "and": "&#32;",
+       "actions": "saungay",
        "namespaces": "pangangananay a salaedan",
        "variants": "masazumaay",
        "navigation-heading": "pasubana’ tu miidangay pipili’an",
+       "errorpagetitle": "mungangaw",
        "returnto": "tatiku tazuma至 $1.",
        "tagline": "makayzaay i {{SITENAME}}",
        "help": "buhci tu kamu",
        "history_small": "nazipa’an",
        "printableversion": "kapah tu insace baziyong / sapad",
        "permalink": "saluimengay misiket",
+       "print": "insace",
        "view": "ciwsace",
        "view-foreign": "i $1 miciwsace",
-       "edit": "mikawaway tu kalumyiti",
+       "edit": "mikawaway-kalumyiti",
        "create": "patizeng",
        "create-local": "cunusen itiniay a buhci tu kamu",
+       "delete": "misipu",
+       "protect_change": "misumad",
        "newpage": "baluhayay a kasabelih",
        "talkpagelinktext": "sasukamu",
        "personaltools": "teked sakaluk",
        "jumpto": "taayaw:",
        "jumptonavigation": "pasubana’ tu miidangay",
        "jumptosearch": "kilim",
-       "aboutsite": "mahizaay {{SITENAME}}",
-       "aboutpage": "Project:mahizaay u...",
+       "aboutsite": "mahizaay {{SITENAME}}",
+       "aboutpage": "Project:mahizaay",
        "copyright": "anu izaw ku zuma buhci tu kamu, kasabelih aazihen a lacul i labu, pisaungay hamin $1 sapabeli tu kinli a ceding.",
        "copyrightpage": "{{ns:project}}:nisanga’an niza tu tungus a kawaw",
        "currentevents": "ayzaay a sinbun",
        "portal-url": "Project:komiyonityi sacumudan",
        "privacy": "salimek a mikuwanay a kawaw",
        "privacypage": "Project:salimek a mikuwanay a kawaw",
+       "ok": "malucekay",
        "retrievedfrom": "miala i \"$1\"",
-       "editsection": "mikawaway tu kalumyiti",
+       "editsection": "mikawaway-kalumyiti",
        "editold": "mikawaway tu kalumyiti",
        "viewsourceold": "ciwsace sakatizeng bangu",
-       "editlink": "mikawaway tu kalumyiti",
+       "editlink": "mikawaway-kalumyiti",
        "viewsourcelink": "ciwsace sakatizeng bangu",
        "editsectionhint": "mikawaway tu kalumyiti tusil: $1",
        "toc": "dilyikotoling",
        "nstab-special": "sazumaay a kasabelih",
        "nstab-project": "cwanan kasabelih",
        "nstab-image": "tangan",
+       "nstab-mediawiki": "palatuh",
        "nstab-template": "taazihan mitudung",
        "nstab-category": "kakuniza",
        "mainpage-nstab": "saayaway a belih",
+       "error": "mungangaw",
+       "databaseerror-query": "palalitemuh tu kawaw: $1",
+       "databaseerror-function": "sakapaluwaluway: $1",
+       "databaseerror-error": "mungangaw: $1",
        "badtitle": "a’cusay a pyawti",
        "badtitletext": "matuzu’ay a kasabelih pyawti u la’cusay、nayi’ ku cacan, caaysa tatenga’ay tu misiket kamu Wikiay a pyawti.\ntebanay pyawti akay amalyilu la’cusay pisaungay i pyawtayi a tatebanan nu nisulitan.",
        "viewsource": "ciwsace sakatizeng bangu",
+       "exception-nologin": "caay henay patalabu",
+       "welcomeuser": "manamuh tu tayniay, $1!",
+       "yourname": "misaungayay a kalungangan:",
        "userlogin-yourname": "misaungayay a kalungangan",
        "userlogin-yourname-ph": "pisulitan tu nu misay a misaungayay a kalungangan",
        "yourpassword": "mima:",
        "login": "patalabu",
        "nav-login-createaccount": "patalabu / panganganen ku canghaw",
        "logout": "katahkal",
+       "notloggedin": "caay henay patalabu",
        "userlogin-noaccount": "inayi’ ku canghaw kisu haw?",
        "userlogin-joinproject": "micunus {{SITENAME}}",
        "createaccount": "panganganen ku canghaw",
        "userlogin-helplink2": "patalabu miedap",
        "createacct-emailoptional": "imyiyo(email) tigami (u pili’ay sasulitan)",
        "createacct-email-ph": "pisulitan ku imyiyo(email) nu misu",
+       "createacct-reason": "mahicaay",
        "createacct-submit": "panganganen ku misuay a canghaw",
        "createacct-another-submit": "panganganen ku canghaw",
        "createacct-benefit-heading": "{{SITENAME}} paanin tu nisulitan tu nu tapangay mahiza kisuan.",
        "createacct-benefit-body1": "saka{{PLURAL:$1|ku mikawaway tu kalumyiti}}",
        "createacct-benefit-body2": "{{PLURAL:$1| kasabelih}}",
        "createacct-benefit-body3": "cay katenesay{{PLURAL:$1|paaninay tu kalusasing}}",
+       "loginsuccesstitle": "patalabutu",
        "loginlanguagelabel": "kamu: $1",
        "pt-login": "patalabu",
        "pt-login-button": "patalabu",
        "pt-createaccount": "panganganen ku canghaw",
        "pt-userlogout": "katahkal",
+       "botpasswords-label-create": "patizeng",
+       "botpasswords-label-update": "misabaluhay",
        "botpasswords-label-cancel": "palawpes",
+       "botpasswords-label-delete": "masipu",
+       "botpasswords-label-grants-column": "pabeli tu kinli",
        "resetpass-submit-cancel": "palawpes",
        "passwordreset": "miliyaw miteka setin mima",
+       "passwordreset-username": "misaungayay a kalungangan:",
        "changeemail-none": "(nayi’)",
+       "resettokens-tokens": "sabuhat:",
        "bold_sample": "kibetulay a sulit",
        "bold_tip": "kibetulay a sulit",
        "italic_sample": "tukenihay nisulit",
        "sig_tip": "misuay a sulit nu ngangan atu demiad, tuki",
        "hr_tip": "Sapisasuala (cayka yadah kawiza)",
        "summary": "pecu’ nu lacul:",
+       "subject": "taazihan tu kawaw:",
        "minoredit": "payni mikilulay a mikawaway tu kalumyiti",
        "watchthis": "miazih tuyni kasabelih",
        "savearticle": "misuped kasabelih",
-       "preview": "paazih pataayaway miazih",
+       "preview": "pataayaway miazih",
        "showpreview": "paazih pataayaway miazih",
        "showdiff": "paazih ku masumaday",
        "anoneditwarning": "<strong>patalaw:</strong>caay henay kisu patalabu. anu miteka mikawaway tu kalumyiti, IP adolyise nu misu ama mitilak. anu kisu <strong>[$1  patalabu ]</strong> acasa <strong>[$2 panganganen ku canghaw ]</strong>, misuay mikawaway tu kalumyiti payni tu nu misuay misaungayay kalungangan sacuzu’ ,izaway zuma kapahayay.",
        "loginreqlink": "patalabu",
+       "newarticle": "(baluhay)",
        "newarticletext": "masasiket kisu tu nayi’ay tu kasabelih.\namipatizeng tina kasabelih, kaisasa mikawaway tu kalumyiti atilad misulit ku lacul (kahica nu kawaw piazih tu tatenga’ay [$1 misaungay a buhci tu kamu  kasabelih ]).\namahica caay padeteng tayza tina kasabelih kisu haw, pihaymaw sapecec saazihay a <strong>tatiku</strong> pipenecan.",
        "noarticletext": "kina kasabelih inayi’ lacul ayza,kapah tu kisu i zumaay a kasabelih [[Special:Search/{{PAGENAME}}| mikilim kina kasabelih pyawti ]]、<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}  mikilim sasuala nasulitan nakawawan ] caay sa[{{fullurl:{{FULLPAGENAME}}|action=edit}} patizeng kina kasabelih ]</span>.",
        "noarticletext-nopermission": "tina kasabelih ayza inayi’ lacul,\nkapah tu kisu i zuma kasabelih [[Special:Search/{{PAGENAME}}| kilim kina kasabelih pyawti ]],acasa <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}}  kilim sasuala nasulitan nakawawan ]</span>,uyzasa  inayi’  ku tungus patizeng tina kasabelih.",
+       "updated": "(misabaluhay)",
        "editing": "mikawaway tu kalumyiti  $1 ayza",
        "creating": "patizeng ayza $1",
        "editingsection": "mikawaway tu kalumyiti ayza $1 (tusil)",
+       "yourdiff": "sasizuma",
        "templatesused": "uyni kasabelih pisaungay tu isasaay {{PLURAL:$1|taazihan mitudung}}:",
        "template-protected": "(madiputay)",
        "template-semiprotected": "(madiputay a kasabelih - satizep mikawaway tu kalumyiti)",
        "hiddencategories": "kina kasabelih tungusay nu {{PLURAL:$1|1 midimut kakuniza }}mamikawaw:",
        "permissionserrorstext-withaction": "namakay isasaay {{PLURAL:$1|mahicaay}}, inayi’ kisu situngus miteka $2 miteka tuway misaungay:",
        "moveddeleted-notice": "kina kasabelih masipu tu.\nisasa nipabeli kina kasabelihay a masipu atu milimad nasulitan nakawawan, taneng miazih tu tatenga’ay.",
+       "content-model-text": "sulit a dada’",
+       "content-json-empty-object": "inayi’ay a tuutuud",
+       "content-json-empty-array": "inayi’ay a papazengan tu nisulitan",
        "viewpagelogs": "ciwsace kina kasabelih a nasulitan nakawawan",
        "currentrev-asof": "i $1 a sabaluhay masumad",
        "revisionasof": "$1 a sumad",
        "nextrevision": "kilulay masumad →",
        "currentrevisionlink": "sabaluhay masumad",
        "cur": "ayza",
+       "next": "nuzikuzan",
        "last": "ayaway",
+       "page_last": "sazikuzay a kasabelih",
+       "histfirst": "sakasumamadan",
+       "histlast": "sabaluhay",
+       "historyempty": "(inayi’)",
+       "history-feed-item-nocomment": "$1 i $2",
        "rev-showdeleted": "paazih",
        "revdelete-show-file-submit": "hang",
        "revdelete-radio-set": "midimut",
+       "revdelete-log": "mahicaay:",
+       "mergehistory-reason": "mahicaay:",
        "history-title": "\"$1\" masumaday a nazipa’an",
        "difference-title": "\"$1\" misumad laeday sasizuma",
        "lineno": "silsil $1:",
        "searchresults-title": "$1 heci nu makatepa",
        "prevn": "ayaw saka {{PLURAL:$1|$1}}",
        "nextn": "zikuzan saka {{PLURAL:$1|$1}}",
+       "prev-page": "ayaway a belih",
+       "next-page": "zikuzan a belih",
        "nextn-title": "nuzikuzan saka {{PLURAL:$1|a heci}}",
        "shown-title": "paybelih {{PLURAL:$1|$1 ku heci}} paazih",
        "viewprevnext": "ciwsace ($1 {{int:pipe-separator}} $2) ($3)",
        "search-redirect": "(miliyaw tazuma namakay $1)",
        "search-section": "(tusil $1)",
        "search-suggest": "u tuzu’ nu misu ku:$1 haw?",
+       "search-interwiki-more": "(yadah)",
+       "search-relatedarticle": "mahizaay",
        "searchall": "hamin",
        "search-showingresults": "{{PLURAL:$4|saka <strong>$1</strong> a heci, pulung <strong>$3</strong>|saka <strong>$1-$2</strong> a heci, pulung <strong>$3</strong>}}",
        "search-nonefound": "nayi’ matatungusay palalitemuh tu kawaw maheciay.",
        "powersearch-toggleall": "hamin",
        "powersearch-togglenone": "nayi’",
        "mypreferences": "setin tu kanamuhan",
-       "skin-preview": "paazih pataayaway miazih",
+       "prefs-skin": "nuhekalan",
+       "skin-preview": "pataayaway miazih",
+       "datedefault": "sulyang nu pataayaw tu kawaw",
        "prefs-rc": "capi a demaiday a sumad",
-       "prefs-editing": "mikawaway tu kalumyiti",
+       "prefs-misc": "zuma",
+       "prefs-rendering": "nuhekalan",
+       "saveprefs": "suped",
+       "prefs-editing": "mikawaway-kalumyiti",
        "searchresultshead": "kilim",
+       "stub-threshold-sample-link": "maaziahan",
        "stub-threshold-disabled": "mapasatezep",
+       "timezoneregion-africa": "Afilika",
+       "timezoneregion-america": "Amilikaco",
+       "timezoneregion-antarctica": "Nancico",
+       "timezoneregion-arctic": "Sasaamisan",
+       "timezoneregion-asia": "Yaco",
+       "timezoneregion-australia": "Awco",
+       "timezoneregion-europe": "Oco",
+       "timezoneregion-indian": "Intuyang-bayu’",
+       "timezoneregion-pacific": "Taypinyang-bayu’",
        "prefs-searchoptions": "kilim",
+       "default": "pataayaw tu kawaw",
+       "prefs-custom-css": "pakuniza misanga’ CSS",
        "yourlanguage": "kamu:",
-       "prefs-editor": "mikawaway tu kalumyiti",
-       "prefs-preview": "paazih pataayaway miazih",
+       "prefs-i18n": "masakitakiay",
+       "prefs-signature": "sulitan a ngangan",
+       "prefs-timeoffset": "ilaed nu tuki",
+       "prefs-editor": "mikawaway-kalumyitiay",
+       "prefs-preview": "pataayaway miazih",
+       "prefs-tokenwatchlist": "sabuhat",
+       "prefs-diffs": "sasizuma",
+       "userrights-reason": "mahicaay:",
+       "userrights-expiry-current": "kakatekuhan $1",
+       "userrights-expiry": "kakatekuhan:",
+       "group": "luyaluy:",
+       "group-user": "misaungayay",
+       "group-bot": "kikay a tademaw",
+       "group-sysop": "mikuwanay",
+       "group-bureaucrat": "situngusay a mikawaway",
        "group-all": "(hamin)",
+       "group-bot-member": "{{GENDER:$1|kikay a tademaw}}",
+       "grouppage-bot": "{{ns:project}}:kikay a tademaw",
+       "grouppage-sysop": "{{ns:project}}:mikuwanay",
+       "grouppage-bureaucrat": "{{ns:project}}:situngusay a mikawaway",
        "right-writeapi": "pisaungay suliten API",
        "grant-createaccount": "panganganen ku canghaw",
        "newuserlogpage": "patizeng misaungayay nasulitan nakawawan",
        "recentchanges-legend-heading": "<strong>u tinaku nu kulit:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (piazih tu tatenga’ay [[Special:NewPages| baluhayay a kasabelih]])",
        "recentchanges-submit": "paazih",
+       "rcfilters-savedqueries-new-name-label": "kalungangan",
        "rcfilters-savedqueries-cancel-label": "palawpes",
        "rcfilters-filterlist-title": "kilim",
+       "rcfilters-filter-user-experience-level-newcomer-label": "baluhayay a misaungayay",
+       "rcfilters-filter-user-experience-level-learner-label": "mahananamay",
+       "rcfilters-filter-bots-label": "kikay a tademaw",
+       "rcfilters-filter-patrolled-label": "tayza mikibi tuway",
+       "rcfilters-filter-minor-label": "cayka yadah ku misumad",
        "rclistfrom": "paazih nay $3 $2 baluhayay a sumad katukuh ayza",
        "rcshowhideminor": "$1 mikilulay mikawaway tu kalumyiti",
        "rcshowhideminor-show": "paazih",
        "newpageletter": "baluhay",
        "boteditletter": "kikay a tademaw",
        "rc-change-size-new": "masumadtu sa u $1 {{PLURAL:$1|wyiyincu}}",
+       "newsectionsummary": "/* $1 */ baluhay a tusil",
        "recentchangeslinked": "sasuala a sumad",
        "recentchangeslinked-feed": "sasuala a sumad",
        "recentchangeslinked-toolbox": "sasuala a sumad",
        "recentchangeslinked-page": "kasabelih kalungangan:",
        "recentchangeslinked-to": "Show changes to pages linked to the given page instead\nmisumad ku paazih masasiket tayza matuzu’ay kasabelih a nisumad",
        "upload": "patapabaw ku tangan",
+       "uploadnologin": "caay henay patalabu",
        "filedesc": "pecu’ nu lacul",
+       "fileuploadsummary": "pecu’ nu lacul:",
+       "filesource": "saangangan:",
        "upload-dialog-button-cancel": "palawpes",
+       "upload-dialog-button-back": "tatiku",
+       "upload-dialog-button-done": "pahezek",
+       "upload-dialog-button-save": "suped",
+       "upload-dialog-button-upload": "patapabaw",
+       "upload-form-label-infoform-name": "kalungangan",
        "upload-form-label-infoform-description": "patahkal",
        "upload-form-label-infoform-categories": "kakuniza",
        "upload-form-label-infoform-date": "demiad",
        "license": "sapabeli tu kinli a cedang",
        "license-header": "sapabeli tu kinli a cedang",
+       "listfiles-delete": "misipu",
        "imgfile": "tangan",
        "listfiles": "tangan-tangan misiket",
+       "listfiles_thumb": "sukep tu zunga",
        "listfiles_date": "demiad",
+       "listfiles_name": "kalungangan",
+       "listfiles_user": "misaungayay",
+       "listfiles_size": "hacica-tabaki",
        "listfiles_description": "patahkal",
+       "listfiles_count": "baziyong",
        "listfiles-latestversion-yes": "hang",
        "listfiles-latestversion-no": "caay",
        "file-anchor-link": "tangan",
        "linkstoimage": "isasaay {{PLURAL:$1| kasabelih  misiket |saka $1 a kasabelih misiket}}katukuh tina tangan:",
        "nolinkstoimage": "nayi’ ku kasabelih masasiket katukuh tini a tangan.",
        "sharedupload-desc-here": "kina tangan nay $1 hakay satu pisaungay tu zuma a cwanan.\nisasaay paazih kuyniay a tangan i [$2 tangan patahkal kasabelih] a patahkalay a lacul.",
+       "shared-repo-from": "nay $1",
        "upload-disallowed-here": "la’cus kisu mitahpu tuyni a tangan.",
+       "filerevert-comment": "mahicaay:",
+       "filedelete-comment": "mahicaay:",
+       "filedelete-submit": "masipu",
+       "download": "patasasa'",
        "randompage": "kakibalucu’ ay a kasabelih",
        "randomincategory-submit": "mileku",
+       "statistics": "sausi",
        "pageswithprop-submit": "mileku",
+       "brokenredirects-delete": "misipu",
+       "withoutinterwiki-legend": "saayaway a sulit",
        "withoutinterwiki-submit": "paazih",
        "nbytes": "$1 {{PLURAL:$1|wyiyincu}}",
        "ncategories": "{{PLURAL:$1|kakuniza}}",
        "nmembers": "$1 {{PLURAL:$1|ku mamikawaw}}",
        "prefixindex-submit": "paazih",
        "protectedpages-page": "kasabelih",
+       "protectedpages-expiry": "kakatekuhan",
+       "protectedpages-reason": "mahicaay",
        "newpages": "baluhay kasabelih",
        "newpages-submit": "paazih",
        "move": "milimad",
+       "notargettitle": "inayi’ ku pabalucu’an",
        "pager-older-n": "{{PLURAL:$1| kusa malumanay}}",
        "suppress": "malangat",
+       "apisandbox-reset": "palawpis",
+       "apisandbox-retry": "miliyaw mitaneng",
+       "apisandbox-examples": "tinaku",
+       "apisandbox-results": "heci",
        "apisandbox-continue": "palalid",
+       "apisandbox-continue-clear": "palawpis",
        "booksources": "nu cudad atu laculaculan",
        "booksources-search-legend": "mikilim ku cudad atu laculaculan",
        "booksources-search": "kilim",
        "log": "nasulitan nakawawan",
        "logeventslist-submit": "paazih",
+       "checkbox-select": "mipili’: $1",
        "checkbox-all": "hamin",
        "checkbox-none": "nayi’",
        "allpages": "hamin nu kasabelih",
+       "nextpage": "zikuzan a belih ($1)",
+       "prevpage": "ayaway a belih ($1)",
        "allarticles": "hamin nu kasabelih",
        "allpagessubmit": "mileku",
        "categories": "kakuniza",
        "categories-submit": "paazih",
+       "linksearch-ns": "pangangananay a salaedan:",
        "linksearch-ok": "kilim",
        "listusers-submit": "paazih",
+       "listgrouprights-group": "luyaluy",
+       "listgrants": "pabeli tu kinli",
+       "listgrants-rights": "kinli",
+       "emailusernamesubmit": "patayzaan",
+       "emailfrom": "nay:",
+       "emailto": "katukuh:",
+       "emailsubject": "taazihan tu kawaw:",
+       "emailmessage": "palatuh:",
+       "emailsend": "patigamitu",
        "watchlist": "miazihay a piazihan tu sulit",
        "mywatchlist": "miazihay a piazihan tu sulit",
+       "watchnologin": "caay henay patalabu",
        "watch": "miazih",
   &nb