Merge "Move around "ا" to after "آ" and not before"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 6 Sep 2017 13:12:13 +0000 (13:12 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 6 Sep 2017 13:12:13 +0000 (13:12 +0000)
596 files changed:
.gitignore
HISTORY
RELEASE-NOTES-1.30
autoload.php
composer.json
docs/hooks.txt
docs/ontology.owl [new file with mode: 0644]
docs/uidesign/child-selector-emu.html [deleted file]
includes/Block.php
includes/CategoriesRdf.php [new file with mode: 0644]
includes/Category.php
includes/CommentStore.php [new file with mode: 0644]
includes/CommentStoreComment.php [new file with mode: 0644]
includes/DefaultSettings.php
includes/EditPage.php
includes/FeedUtils.php
includes/Linker.php
includes/MagicWordArray.php
includes/MediaWiki.php
includes/MergeHistory.php
includes/MovePage.php
includes/OutputPage.php
includes/PHPVersionCheck.php
includes/PageProps.php
includes/PreConfigSetup.php [new file with mode: 0644]
includes/Preferences.php
includes/Revision.php
includes/SiteStats.php
includes/StreamFile.php
includes/Title.php
includes/WatchedItemQueryService.php
includes/WebStart.php
includes/WikiMap.php
includes/actions/CreditsAction.php
includes/actions/InfoAction.php
includes/api/ApiBase.php
includes/api/ApiEditPage.php
includes/api/ApiHelp.php
includes/api/ApiMove.php
includes/api/ApiParamInfo.php
includes/api/ApiQuery.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBacklinks.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryCategoryInfo.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryDuplicateFiles.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryInfo.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryProtectedTitles.php
includes/api/ApiQueryQueryPage.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiSetNotificationTimestamp.php
includes/api/ApiUpload.php
includes/api/i18n/de.json
includes/api/i18n/en.json
includes/api/i18n/es.json
includes/api/i18n/eu.json
includes/api/i18n/fa.json
includes/api/i18n/fr.json
includes/api/i18n/gl.json
includes/api/i18n/he.json
includes/api/i18n/it.json
includes/api/i18n/ja.json
includes/api/i18n/ko.json
includes/api/i18n/lb.json
includes/api/i18n/pt-br.json
includes/api/i18n/pt.json
includes/api/i18n/qqq.json
includes/api/i18n/zh-hans.json
includes/api/i18n/zh-hant.json
includes/auth/AuthManager.php
includes/auth/CreateFromLoginAuthenticationRequest.php
includes/cache/HTMLFileCache.php
includes/cache/LinkBatch.php
includes/cache/MessageCache.php
includes/cache/localisation/LocalisationCache.php
includes/changes/CategoryMembershipChange.php
includes/changes/ChangesListBooleanFilter.php
includes/changes/EnhancedChangesList.php
includes/changes/RecentChange.php
includes/collation/IcuCollation.php
includes/compat/CdbCompat.php [deleted file]
includes/compat/IPSetCompat.php [deleted file]
includes/compat/MemcachedClientCompat.php [deleted file]
includes/compat/RunningStatCompat.php [deleted file]
includes/config/EtcdConfig.php
includes/config/EtcdConfigParseError.php [new file with mode: 0644]
includes/dao/DBAccessObjectUtils.php
includes/dao/IDBAccessObject.php
includes/db/DatabaseOracle.php
includes/debug/logger/monolog/WikiProcessor.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksDeletionUpdate.php
includes/deferred/LinksUpdate.php
includes/diff/DifferenceEngine.php
includes/exception/MWExceptionRenderer.php
includes/export/WikiExporter.php
includes/export/XmlDumpWriter.php
includes/externalstore/ExternalStoreDB.php
includes/filerepo/FileBackendDBRepoWrapper.php
includes/filerepo/ForeignAPIRepo.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLFormFieldWithButton.php
includes/http/MWHttpRequest.php
includes/import/WikiImporter.php
includes/import/WikiRevision.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/MssqlInstaller.php
includes/installer/MysqlInstaller.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/PostgresInstaller.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/cs.json
includes/installer/i18n/da.json
includes/installer/i18n/el.json
includes/installer/i18n/es.json
includes/installer/i18n/eu.json
includes/installer/i18n/fr.json
includes/installer/i18n/gl.json
includes/installer/i18n/ko.json
includes/installer/i18n/lb.json
includes/installer/i18n/nb.json
includes/installer/i18n/nl.json
includes/installer/i18n/pt-br.json
includes/installer/i18n/uk.json
includes/installer/i18n/vi.json
includes/jobqueue/JobQueueDB.php
includes/jobqueue/jobs/CategoryMembershipChangeJob.php
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/GenericArrayObject.php
includes/libs/HashRing.php
includes/libs/IP.php
includes/libs/MultiHttpClient.php
includes/libs/filebackend/HTTPFileStreamer.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/HashBagOStuff.php
includes/libs/objectcache/MultiWriteBagOStuff.php
includes/libs/objectcache/RESTBagOStuff.php
includes/libs/objectcache/WANObjectCache.php
includes/libs/objectcache/WANObjectCacheReaper.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/TransactionProfiler.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabaseMysql.php
includes/libs/rdbms/database/DatabaseMysqlBase.php
includes/libs/rdbms/database/DatabaseMysqli.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/position/MySQLMasterPos.php
includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php
includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
includes/libs/rdbms/database/utils/NextSequenceValue.php [new file with mode: 0644]
includes/libs/rdbms/exception/DBConnectionError.php
includes/libs/rdbms/exception/DBError.php
includes/libs/rdbms/exception/DBExpectedError.php
includes/libs/rdbms/exception/DBQueryError.php
includes/libs/rdbms/exception/DBTransactionSizeError.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadmonitor/ILoadMonitor.php
includes/libs/rdbms/loadmonitor/LoadMonitor.php
includes/libs/stats/BufferingStatsdDataFactory.php
includes/libs/stats/IBufferingStatsdDataFactory.php
includes/libs/stats/NullStatsdDataFactory.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/Article.php
includes/page/PageArchive.php
includes/page/WikiPage.php
includes/parser/CoreParserFunctions.php
includes/parser/Parser.php
includes/parser/ParserOutput.php
includes/parser/Preprocessor_Hash.php
includes/rcfeed/FormattedRCFeed.php
includes/rcfeed/IRCColourfulRCFeedFormatter.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/revisiondelete/RevDelArchivedFileItem.php
includes/revisiondelete/RevDelItem.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevDelLogItem.php
includes/revisiondelete/RevDelLogList.php
includes/revisiondelete/RevisionDeleter.php
includes/services/ServiceContainer.php
includes/session/CookieSessionProvider.php
includes/site/DBSiteStore.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specialpage/AuthManagerSpecialPage.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialBlock.php
includes/specials/SpecialChangeContentModel.php
includes/specials/SpecialContributions.php
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialNewimages.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialPageLanguage.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialUpload.php
includes/specials/SpecialWatchlist.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewPagesPager.php
includes/specials/pagers/ProtectedPagesPager.php
includes/tidy/Balancer.php
includes/tidy/RemexCompatMunger.php
includes/upload/UploadBase.php
includes/upload/UploadFromUrl.php
includes/upload/UploadStash.php
includes/user/User.php
includes/utils/BatchRowIterator.php
includes/utils/UIDGenerator.php
includes/widget/SearchInputWidget.php
includes/widget/SelectWithInputWidget.php
languages/Language.php
languages/data/Names.php
languages/i18n/af.json
languages/i18n/ais.json [new file with mode: 0644]
languages/i18n/ar.json
languages/i18n/as.json
languages/i18n/ast.json
languages/i18n/az.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bn.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/cdo.json
languages/i18n/ce.json
languages/i18n/ckb.json
languages/i18n/cs.json
languages/i18n/csb.json
languages/i18n/da.json
languages/i18n/de-formal.json
languages/i18n/de.json
languages/i18n/din.json
languages/i18n/el.json
languages/i18n/en-gb.json
languages/i18n/en.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/gl.json
languages/i18n/gor.json
languages/i18n/gu.json
languages/i18n/hak.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hr.json
languages/i18n/hsb.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/ilo.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jv.json
languages/i18n/kab.json
languages/i18n/khw.json
languages/i18n/km.json
languages/i18n/ko.json
languages/i18n/ku-latn.json
languages/i18n/lb.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/lzh.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/mwl.json
languages/i18n/my.json
languages/i18n/nan.json
languages/i18n/nb.json
languages/i18n/ne.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/oc.json
languages/i18n/or.json
languages/i18n/pl.json
languages/i18n/pnb.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/rif.json
languages/i18n/ro.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/sd.json
languages/i18n/shi.json
languages/i18n/shn.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/tay.json
languages/i18n/th.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/vi.json
languages/i18n/wuu.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesTay.php [new file with mode: 0644]
maintenance/Maintenance.php
maintenance/archives/patch-categorylinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/archives/patch-imagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-iwlinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-l10n_cache-primary-key.sql [new file with mode: 0644]
maintenance/archives/patch-langlinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-log_search-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-log_search-rename-index.sql [deleted file]
maintenance/archives/patch-module_deps-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-objectcache-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-pagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-querycache_info-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-site_stats-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-templatelinks-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-text-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-transcache-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-user_former_groups-fix-pk.sql [new file with mode: 0644]
maintenance/archives/patch-user_properties-fix-pk.sql [new file with mode: 0644]
maintenance/deleteOldRevisions.php
maintenance/deleteOrphanedRevisions.php
maintenance/deleteRevision.php [deleted file]
maintenance/doMaintenance.php
maintenance/dumpCategoriesAsRdf.php [new file with mode: 0644]
maintenance/fixExtLinksProtocolRelative.php
maintenance/importDump.php
maintenance/migrateComments.php [new file with mode: 0644]
maintenance/namespaceDupes.php
maintenance/oracle/archives/patch-auto_increment_triggers.sql [new file with mode: 0644]
maintenance/oracle/tables.sql
maintenance/orphans.php
maintenance/populateIpChanges.php [new file with mode: 0644]
maintenance/postgres/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/postgres/tables.sql
maintenance/rebuildrecentchanges.php
maintenance/resources/update-oojs-ui.sh
maintenance/sqlite/archives/patch-categorylinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-imagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-iwlinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-l10n_cache-primary-key.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-langlinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-log_search-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-log_search-rename-index.sql [deleted file]
maintenance/sqlite/archives/patch-module_deps-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-objectcache-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-pagelinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-querycache_info-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-site_stats-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-templatelinks-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-text-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-transcache-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-user_former_groups-fix-pk.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-user_properties-fix-pk.sql [new file with mode: 0644]
maintenance/tables.sql
package.json
phpcs.xml
resources/Resources.php
resources/lib/jquery/jquery.migrate.js
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/en.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/ja.json
resources/lib/oojs-ui/i18n/kab.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/ko.json
resources/lib/oojs-ui/i18n/lb.json
resources/lib/oojs-ui/i18n/li.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/nn.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/skr-arab.json [new file with mode: 0644]
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/yue.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/oojs-ui-windows.js.map
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/lib/oojs-ui/wikimedia-ui-base.less [new file with mode: 0644]
resources/src/jquery/jquery.badge.css
resources/src/jquery/jquery.tablesorter.js
resources/src/mediawiki.action/mediawiki.action.edit.js
resources/src/mediawiki.action/mediawiki.action.edit.preview.js
resources/src/mediawiki.action/mediawiki.action.edit.styles.css [deleted file]
resources/src/mediawiki.action/mediawiki.action.edit.styles.less [new file with mode: 0644]
resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
resources/src/mediawiki.action/mediawiki.action.view.postEdit.less
resources/src/mediawiki.legacy/commonPrint.css
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki.less/mediawiki.ui/variables.less
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueriesModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueryItemModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.UriProcessor.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/mw.rcfilters.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.mixins.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.highlightCircles.seenunseen.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ChangesListWrapperWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterMenuSectionOptionWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterTagMultiselectWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.HighlightColorPickerWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.ItemMenuOptionWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.MenuSelectWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.RcTopSectionWidget.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListItemWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.TagItemWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.WatchlistTopSectionWidget.less [new file with mode: 0644]
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.variables.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesLimitPopupWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.MarkSeenButtonWidget.js [new file with mode: 0644]
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.RcTopSectionWidget.js [new file with mode: 0644]
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.SaveFiltersPopupButtonWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.SavedLinksListItemWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.TagItemWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.WatchlistTopSectionWidget.js [new file with mode: 0644]
resources/src/mediawiki.skinning/content.parsoid.less
resources/src/mediawiki.special/mediawiki.special.apisandbox.css
resources/src/mediawiki.special/mediawiki.special.changeslist.legend.js
resources/src/mediawiki.special/mediawiki.special.search.interwikiwidget.styles.less
resources/src/mediawiki.toolbar/images/ksh/LICENSE
resources/src/mediawiki.ui/components/icons.less
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResultWidget.css
resources/src/mediawiki.widgets/mw.widgets.SelectWithInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.StashedFileWidget.less
resources/src/mediawiki.widgets/mw.widgets.TitleWidget.less
resources/src/mediawiki.widgets/mw.widgets.UsersMultiselectWidget.js
resources/src/mediawiki/htmlform/styles.css
resources/src/mediawiki/mediawiki.apihelp.css
resources/src/mediawiki/mediawiki.js
tests/parser/ParserTestRunner.php
tests/parser/parserTests.txt
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/data/categoriesrdf/categoriesRdf-out.nt [new file with mode: 0644]
tests/phpunit/includes/CommentStoreTest.php [new file with mode: 0644]
tests/phpunit/includes/PageArchiveTest.php [new file with mode: 0644]
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php
tests/phpunit/includes/WikiMapTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/changes/ChangesListStringOptionsFilterGroupTest.php
tests/phpunit/includes/changes/RecentChangeTest.php
tests/phpunit/includes/changes/TestRecentChangesHelper.php
tests/phpunit/includes/collation/CollationFaTest.php [new file with mode: 0644]
tests/phpunit/includes/config/EtcdConfigTest.php
tests/phpunit/includes/deferred/LinksUpdateTest.php
tests/phpunit/includes/libs/IPTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.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/phpunit/maintenance/categoriesRdfTest.php [new file with mode: 0644]
tests/phpunit/structure/ApiDocumentationTest.php [deleted file]
tests/phpunit/structure/ApiStructureTest.php [new file with mode: 0644]
tests/phpunit/suites/ParserTestTopLevelSuite.php
tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js
tests/selenium/wdio.conf.js

index 388f354..b991e11 100644 (file)
@@ -42,6 +42,7 @@ sftp-config.json
 /StartProfiler.php
 
 # Building & testing
+npm-debug.log
 node_modules/
 /tests/phpunit/phpunit.phar
 
diff --git a/HISTORY b/HISTORY
index 9259814..0a2869d 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -699,6 +699,17 @@ There's usually someone online in #mediawiki on irc.freenode.net.
 
 = MediaWiki 1.27 =
 
+== MediaWiki 1.27.3 ==
+Due to a packaging error, the wrong version of the SyntaxHighlight extension was
+included in the tarball version of MediaWiki 1.27.2. The version included had a
+serious security issue in it (T158689). There was also some minor code fixes in
+MediaWiki itself since 1.27.2, but none of them were security relevant.
+
+=== Changes since 1.27.2 ===
+* (T145664) Fix broken wincache merge() implementation
+* (T163434) Add wikimedia/testing-access-wrapper for forwards compatibility
+* (T153505) Fix php warnings on php 7.1 due to use of &$this
+
 == MediaWiki 1.27.2 ==
 This is a security and maintenance release of the MediaWiki 1.27 branch.
 
index dd39561..b478f51 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
@@ -33,11 +35,19 @@ section).
   use $wgFragmentMode to migrate off it to a modern alternative.
 * $wgExternalInterwikiFragmentMode was introduced to control how fragments in
   sinterwikis going outside of current wiki farm are encoded.
+* (T120333) Soft-deprecated the use of PHP extension 'mysql' in favor of 'mysqli'.
+  This PHP extension was deprecated in PHP 5.5 and removed in PHP 7.0. MediaWiki
+  auto-selects the 'mysqli' driver since MediaWiki 1.22, except if explicitly
+  requested through the configuration parameter $wgDBservers.
+* $wgOOUIEditPage was removed, as it is now the default. This was documented as a
+  temporary variable during the migration period.
 
 === New features in 1.30 ===
 * (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
@@ -55,11 +65,11 @@ section).
   just been unwatched.
 * Added $wgParserTestMediaHandlers, where mock media handlers can be passed to
   MediaHandlerFactory for parser tests.
-
-=== Languages updated in 1.30 ===
-
-* Support for kbp (Kabɩyɛ / Kabiyè) was added.
-* Support for skr (Saraiki, سرائیکی) was added.
+* Edit summaries, block reasons, and other "comments" are now stored in a
+  separate database table. Use the CommentFormatter class to access them.
+** This is currently gated by $wgCommentTableSchemaMigrationStage. Most wikis
+   can set this to MIGRATION_NEW and run maintenance/migrateComments.php as
+   soon as any necessary extensions are updated.
 
 === External library changes in 1.30 ===
 
@@ -105,7 +115,9 @@ MediaWiki supports over 350 languages. Many localisations are updated
 regularly. Below only new and removed languages are listed, as well as
 changes to languages because of Phabricator reports.
 
-* …
+* Added: kbp (Kabɩyɛ / Kabiyè)
+* Added: skr (Saraiki, سرائیکی)
+* Added: tay (Tayal / Atayal)
 
 ==== Pig Latin added ====
 * (T45547) Added Pig Latin, a made-up English variant (en-x-piglatin),
@@ -149,6 +161,7 @@ changes to languages because of Phabricator reports.
   WikiPage::makeParserOptions() to create the ParserOptions object and only
   change options that affect the parser cache key.
 * Article::viewRedirect() is deprecated.
+* IP::isValidBlock() was deprecated. Use the equivalent IP::isValidRange().
 * DeprecatedGlobal no longer supports passing in a direct value, it requires a
   callable factory function or a class name.
 * The $parserMemc global, wfGetParserCacheStorage(), and ParserCache::singleton()
@@ -171,6 +184,31 @@ changes to languages because of Phabricator reports.
 * Removed 'jquery.mwExtension' module. (deprecated since 1.26)
 * mediawiki.ui: Deprecate greys, which are not part of WikimediaUI color palette
   any more.
+* CdbReader, CdbWriter, CdbException classes (deprecated in 1.25) were removed.
+  The namespaced classes in the Cdb namespace should be used instead.
+* IPSet class (deprecated in 1.26) was removed. The namespaced IPSet\IPSet
+  should be used instead.
+* RunningStat class (deprecated in 1.27) was removed. The namespaced
+  RunningStat\RunningStat should be used instead.
+* MWMemcached and MemCachedClientforWiki classes (deprecated in 1.27) were removed.
+  The MemcachedClient class should be used instead.
+* 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,
+  $uniq_prefix was deprecated in 1.26 and has now been removed.
+* (T172514) The following tables have had their UNIQUE indexes turned into proper
+  PRIMARY KEYs for increased maintainability: categorylinks, imagelinks, iwlinks,
+  langlinks, log_search, module_deps, objectcache, pagelinks, query_cache, site_stats,
+  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.
 
 == Compatibility ==
 MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for
index d9e85bd..c7f13d5 100644 (file)
@@ -219,6 +219,7 @@ $wgAutoloadLocalClasses = [
        'CachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/CachedBagOStuff.php',
        'CachingSiteStore' => __DIR__ . '/includes/site/CachingSiteStore.php',
        'CapsCleanup' => __DIR__ . '/maintenance/cleanupCaps.php',
+       'CategoriesRdf' => __DIR__ . '/includes/CategoriesRdf.php',
        'Category' => __DIR__ . '/includes/Category.php',
        'CategoryFinder' => __DIR__ . '/includes/CategoryFinder.php',
        'CategoryMembershipChange' => __DIR__ . '/includes/changes/CategoryMembershipChange.php',
@@ -226,9 +227,6 @@ $wgAutoloadLocalClasses = [
        'CategoryPage' => __DIR__ . '/includes/page/CategoryPage.php',
        'CategoryPager' => __DIR__ . '/includes/specials/pagers/CategoryPager.php',
        'CategoryViewer' => __DIR__ . '/includes/CategoryViewer.php',
-       'CdbException' => __DIR__ . '/includes/compat/CdbCompat.php',
-       'CdbReader' => __DIR__ . '/includes/compat/CdbCompat.php',
-       'CdbWriter' => __DIR__ . '/includes/compat/CdbCompat.php',
        'CdnCacheUpdate' => __DIR__ . '/includes/deferred/CdnCacheUpdate.php',
        'CdnPurgeJob' => __DIR__ . '/includes/jobqueue/jobs/CdnPurgeJob.php',
        'CentralIdLookup' => __DIR__ . '/includes/user/CentralIdLookup.php',
@@ -278,6 +276,8 @@ $wgAutoloadLocalClasses = [
        'CollationFa' => __DIR__ . '/includes/collation/CollationFa.php',
        'CommandLineInc' => __DIR__ . '/maintenance/commandLine.inc',
        'CommandLineInstaller' => __DIR__ . '/maintenance/install.php',
+       'CommentStore' => __DIR__ . '/includes/CommentStore.php',
+       'CommentStoreComment' => __DIR__ . '/includes/CommentStoreComment.php',
        'CompareParserCache' => __DIR__ . '/maintenance/compareParserCache.php',
        'CompareParsers' => __DIR__ . '/maintenance/compareParsers.php',
        'ComposerHookHandler' => __DIR__ . '/includes/composer/ComposerHookHandler.php',
@@ -367,7 +367,6 @@ $wgAutoloadLocalClasses = [
        'DeleteLogFormatter' => __DIR__ . '/includes/logging/DeleteLogFormatter.php',
        'DeleteOldRevisions' => __DIR__ . '/maintenance/deleteOldRevisions.php',
        'DeleteOrphanedRevisions' => __DIR__ . '/maintenance/deleteOrphanedRevisions.php',
-       'DeleteRevision' => __DIR__ . '/maintenance/deleteRevision.php',
        'DeleteSelfExternals' => __DIR__ . '/maintenance/deleteSelfExternals.php',
        'DeletedContribsPager' => __DIR__ . '/includes/specials/pagers/DeletedContribsPager.php',
        'DeletedContributionsPage' => __DIR__ . '/includes/specials/SpecialDeletedContributions.php',
@@ -401,6 +400,7 @@ $wgAutoloadLocalClasses = [
        'Dump7ZipOutput' => __DIR__ . '/includes/export/Dump7ZipOutput.php',
        'DumpBZip2Output' => __DIR__ . '/includes/export/DumpBZip2Output.php',
        'DumpBackup' => __DIR__ . '/maintenance/dumpBackup.php',
+       'DumpCategoriesAsRdf' => __DIR__ . '/maintenance/dumpCategoriesAsRdf.php',
        'DumpDBZip2Output' => __DIR__ . '/includes/export/DumpDBZip2Output.php',
        'DumpFileOutput' => __DIR__ . '/includes/export/DumpFileOutput.php',
        'DumpFilter' => __DIR__ . '/includes/export/DumpFilter.php',
@@ -437,6 +437,7 @@ $wgAutoloadLocalClasses = [
        'EraseArchivedFile' => __DIR__ . '/maintenance/eraseArchivedFile.php',
        'ErrorPageError' => __DIR__ . '/includes/exception/ErrorPageError.php',
        'EtcdConfig' => __DIR__ . '/includes/config/EtcdConfig.php',
+       'EtcdConfigParseError' => __DIR__ . '/includes/config/EtcdConfigParseError.php',
        'EventRelayer' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php',
        'EventRelayerGroup' => __DIR__ . '/includes/EventRelayerGroup.php',
        'EventRelayerKafka' => __DIR__ . '/includes/libs/eventrelayer/EventRelayerKafka.php',
@@ -617,7 +618,6 @@ $wgAutoloadLocalClasses = [
        'ILocalizedException' => __DIR__ . '/includes/exception/LocalizedException.php',
        'IMaintainableDatabase' => __DIR__ . '/includes/libs/rdbms/database/IMaintainableDatabase.php',
        'IP' => __DIR__ . '/includes/libs/IP.php',
-       'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
        'IPTC' => __DIR__ . '/includes/media/IPTC.php',
        'IRCColourfulRCFeedFormatter' => __DIR__ . '/includes/rcfeed/IRCColourfulRCFeedFormatter.php',
        'IcuCollation' => __DIR__ . '/includes/collation/IcuCollation.php',
@@ -803,7 +803,6 @@ $wgAutoloadLocalClasses = [
        'MWGrants' => __DIR__ . '/includes/MWGrants.php',
        'MWHttpRequest' => __DIR__ . '/includes/http/MWHttpRequest.php',
        'MWLBFactory' => __DIR__ . '/includes/db/MWLBFactory.php',
-       'MWMemcached' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
        'MWMessagePack' => __DIR__ . '/includes/libs/MWMessagePack.php',
        'MWNamespace' => __DIR__ . '/includes/MWNamespace.php',
        'MWOldPassword' => __DIR__ . '/includes/password/MWOldPassword.php',
@@ -968,7 +967,6 @@ $wgAutoloadLocalClasses = [
        'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
        'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
        'MediaWiki\\Widget\\UsersMultiselectWidget' => __DIR__ . '/includes/widget/UsersMultiselectWidget.php',
-       'MemCachedClientforWiki' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
        'MemcLockManager' => __DIR__ . '/includes/libs/lockmanager/MemcLockManager.php',
        'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
        'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
@@ -987,6 +985,7 @@ $wgAutoloadLocalClasses = [
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
        'MessageLocalizer' => __DIR__ . '/languages/MessageLocalizer.php',
        'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
+       'MigrateComments' => __DIR__ . '/maintenance/migrateComments.php',
        'MigrateFileRepoLayout' => __DIR__ . '/maintenance/migrateFileRepoLayout.php',
        'MigrateUserGroup' => __DIR__ . '/maintenance/migrateUserGroup.php',
        'MimeAnalyzer' => __DIR__ . '/includes/libs/mime/MimeAnalyzer.php',
@@ -1118,6 +1117,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',
@@ -1282,7 +1282,6 @@ $wgAutoloadLocalClasses = [
        'RollbackEdits' => __DIR__ . '/maintenance/rollbackEdits.php',
        'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php',
        'RunJobs' => __DIR__ . '/maintenance/runJobs.php',
-       'RunningStat' => __DIR__ . '/includes/compat/RunningStatCompat.php',
        'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
        'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
        'SamplingStatsdClient' => __DIR__ . '/includes/libs/stats/SamplingStatsdClient.php',
@@ -1669,6 +1668,7 @@ $wgAutoloadLocalClasses = [
        'Wikimedia\\Rdbms\\MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
        'Wikimedia\\Rdbms\\MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
        'Wikimedia\\Rdbms\\MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
+       'Wikimedia\\Rdbms\\NextSequenceValue' => __DIR__ . '/includes/libs/rdbms/database/utils/NextSequenceValue.php',
        'Wikimedia\\Rdbms\\PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
        'Wikimedia\\Rdbms\\PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php',
        'Wikimedia\\Rdbms\\ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
index 32933e6..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.4",
+               "oojs/oojs-ui": "0.23.0",
                "oyejorge/less.php": "1.7.0.14",
                "php": ">=5.5.9",
                "psr/log": "1.0.2",
@@ -37,6 +37,7 @@
                "wikimedia/html-formatter": "1.0.1",
                "wikimedia/ip-set": "1.1.0",
                "wikimedia/php-session-serializer": "1.0.4",
+               "wikimedia/purtle": "1.0.6",
                "wikimedia/relpath": "2.0.0",
                "wikimedia/remex-html": "1.0.1",
                "wikimedia/running-stat": "1.1.0",
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,
diff --git a/docs/ontology.owl b/docs/ontology.owl
new file mode 100644 (file)
index 0000000..6b2e0b7
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE rdf:RDF [
+  <!ENTITY xsd "http://www.w3.org/2001/XMLSchema#">
+  <!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+  <!ENTITY rdfs "http://www.w3.org/2000/01/rdf-schema#">
+  <!ENTITY owl "http://www.w3.org/2002/07/owl#">
+  <!ENTITY mediawiki "https://www.mediawiki.org/ontology#">
+]>
+
+<rdf:RDF
+  xmlns:xsd="&xsd;"
+  xmlns:rdf="&rdf;"
+  xmlns:rdfs="&rdfs;"
+  xmlns:owl="&owl;"
+>
+
+  <owl:Ontology rdf:about="&mediawiki;">
+    <rdfs:label>MediaWiki ontology</rdfs:label>
+    <rdfs:comment>The ontology of MediaWiki</rdfs:comment>
+  </owl:Ontology>
+
+  <!--
+  ///////////////////////////////////////////////////////////////////////////////////////
+  //
+  // Classes
+  //
+  ///////////////////////////////////////////////////////////////////////////////////////
+  -->
+
+  <owl:Class rdf:about="&mediawiki;Dump">
+    <rdfs:label>Dump</rdfs:label>
+    <rdfs:comment>A dump of MediaWiki content.</rdfs:comment>
+  </owl:Class>
+
+  <owl:Class rdf:about="&mediawiki;Category">
+    <rdfs:label>Category</rdfs:label>
+    <rdfs:comment>MediaWiki category.</rdfs:comment>
+  </owl:Class>
+
+  <!--
+  ///////////////////////////////////////////////////////////////////////////////////////
+  //
+  // Properties
+  //
+  ///////////////////////////////////////////////////////////////////////////////////////
+  -->
+
+  <owl:ObjectProperty rdf:about="&mediawiki;isInCategory">
+      <rdfs:label>isInCategory</rdfs:label>
+      <rdfs:comment>One category is the parent of another.</rdfs:comment>
+      <rdfs:range rdf:resource="&mediawiki;Category"/>
+      <rdfs:domain rdf:resource="&mediawiki;Category"/>
+  </owl:ObjectProperty>
+
+</rdf:RDF>
diff --git a/docs/uidesign/child-selector-emu.html b/docs/uidesign/child-selector-emu.html
deleted file mode 100644 (file)
index 9db4c54..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-       <title>CSS Child selector emulation for IE 6</title>
-       <style>
-               /** Common rules **/
-               body  { background-color: #CCC; }
-               table { border:1px black solid; }
-               caption {
-                       background-color: #98fb98;
-                       border:1px solid #40FF40;
-               }
-
-               /** "old" rules" **/
-               table.global th,
-               table.global td
-               {
-                       border: 1px red solid;
-                       background-color:white;
-                       padding:1em;
-               }
-               table.global th
-               {
-                       background-color: #ffc0cb;
-               }
-
-               /** second table. Try to emulate child selector */
-               table.childemu th,
-               table.childemu td {
-                       border: 1px red solid;
-                       background-color:white;
-                       padding:1em;
-               }
-               table.childemu th
-               {
-                       background-color: #ffc0cb;
-               }
-
-               /** Reset style applied in childemu classes */
-               /** TODO: find the real default value!! */
-               table.childemu tr * th,
-               table.childemu tr * td {
-                       border: none;
-                       background-color: transparent;
-                       padding: 0;
-               }
-
-
-               /** child selector emulation */
-       </style>
-</head>
-<body>
-<p>
-The following table show how nested tables inherit colors from the wikitable class (here it was renamed "global").
-</p>
-<table class="global">
-<caption>Global table</caption>
-<tr>
-       <th>TH: should have pink bg</th>
-</tr>
-<tr>
-       <td>TD: white bg</td>
-</tr>
-<tr>
-       <td>
-               <table class="nested">
-               <caption>Nested table</caption>
-               <tr>
-                       <th>Nested TH: transparent</th>
-                       <td>Nested TD: transparent</td>
-               </tr>
-               </table>
-       </td>
-</tr>
-</table>
-
-<p>
-With child selector we could limit the wikitable styling to the direct childs of the table. Unfortunately, Internet Explorer 6.0 does not support child selector. See <a href="https://bugzilla.wikimedia.org/show_bug.cgi?id=33752">our bug #33752</a>.
-</p>
-<table class="childemu">
-<caption>Global table</caption>
-<tr>
-       <th>TH: should have pink bg</th>
-</tr>
-<tr>
-       <td>TD: white bg</td>
-</tr>
-<tr>
-       <td>
-               <table class="nested">
-               <caption>Nested table</caption>
-               <tr>
-                       <th>Nested TH: transparent</th>
-                       <td>Nested TD: transparent</td>
-               </tr>
-               </table>
-       </td>
-</tr>
-</table>
-<p><strong>NOTE:</strong>The nested caption keep the green background. The nested table keep the black border. This is because those declarations are global so we did not reset them.</p>
index 843ea54..40095f1 100644 (file)
@@ -199,6 +199,8 @@ class Block {
        /**
         * Return the list of ipblocks fields that should be selected to create
         * a new block.
+        * @todo Deprecate this in favor of a method that returns tables and joins
+        *  as well, and use CommentStore::getJoin().
         * @return array
         */
        public static function selectFields() {
@@ -207,7 +209,6 @@ class Block {
                        'ipb_address',
                        'ipb_by',
                        'ipb_by_text',
-                       'ipb_reason',
                        'ipb_timestamp',
                        'ipb_auto',
                        'ipb_anon_only',
@@ -218,7 +219,7 @@ class Block {
                        'ipb_block_email',
                        'ipb_allow_usertalk',
                        'ipb_parent_block_id',
-               ];
+               ] + CommentStore::newKey( 'ipb_reason' )->getFields();
        }
 
        /**
@@ -411,7 +412,6 @@ class Block {
                        $this->setBlocker( $row->ipb_by_text );
                }
 
-               $this->mReason = $row->ipb_reason;
                $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
                $this->mAuto = $row->ipb_auto;
                $this->mHideName = $row->ipb_deleted;
@@ -419,7 +419,11 @@ class Block {
                $this->mParentBlockId = $row->ipb_parent_block_id;
 
                // I wish I didn't have to do this
-               $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $row->ipb_expiry );
+               $db = wfGetDB( DB_REPLICA );
+               $this->mExpiry = $db->decodeExpiry( $row->ipb_expiry );
+               $this->mReason = CommentStore::newKey( 'ipb_reason' )
+                       // Legacy because $row probably came from self::selectFields()
+                       ->getCommentLegacy( $db, $row )->text;
 
                $this->isHardblock( !$row->ipb_anon_only );
                $this->isAutoblocking( $row->ipb_enable_autoblock );
@@ -488,8 +492,7 @@ class Block {
                        self::purgeExpired();
                }
 
-               $row = $this->getDatabaseArray();
-               $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" );
+               $row = $this->getDatabaseArray( $dbw );
 
                $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
                $affected = $dbw->affectedRows();
@@ -558,7 +561,7 @@ class Block {
                        // update corresponding autoblock(s) (T50813)
                        $dbw->update(
                                'ipblocks',
-                               $this->getAutoblockUpdateArray(),
+                               $this->getAutoblockUpdateArray( $dbw ),
                                [ 'ipb_parent_block_id' => $this->getId() ],
                                __METHOD__
                        );
@@ -583,14 +586,11 @@ class Block {
 
        /**
         * Get an array suitable for passing to $dbw->insert() or $dbw->update()
-        * @param IDatabase $db
+        * @param IDatabase $dbw
         * @return array
         */
-       protected function getDatabaseArray( $db = null ) {
-               if ( !$db ) {
-                       $db = wfGetDB( DB_REPLICA );
-               }
-               $expiry = $db->encodeExpiry( $this->mExpiry );
+       protected function getDatabaseArray( IDatabase $dbw ) {
+               $expiry = $dbw->encodeExpiry( $this->mExpiry );
 
                if ( $this->forcedTargetID ) {
                        $uid = $this->forcedTargetID;
@@ -603,8 +603,7 @@ class Block {
                        'ipb_user'             => $uid,
                        'ipb_by'               => $this->getBy(),
                        'ipb_by_text'          => $this->getByName(),
-                       'ipb_reason'           => $this->mReason,
-                       'ipb_timestamp'        => $db->timestamp( $this->mTimestamp ),
+                       'ipb_timestamp'        => $dbw->timestamp( $this->mTimestamp ),
                        'ipb_auto'             => $this->mAuto,
                        'ipb_anon_only'        => !$this->isHardblock(),
                        'ipb_create_account'   => $this->prevents( 'createaccount' ),
@@ -616,23 +615,23 @@ class Block {
                        'ipb_block_email'      => $this->prevents( 'sendemail' ),
                        'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
                        'ipb_parent_block_id'  => $this->mParentBlockId
-               ];
+               ] + CommentStore::newKey( 'ipb_reason' )->insert( $dbw, $this->mReason );
 
                return $a;
        }
 
        /**
+        * @param IDatabase $dbw
         * @return array
         */
-       protected function getAutoblockUpdateArray() {
+       protected function getAutoblockUpdateArray( IDatabase $dbw ) {
                return [
                        'ipb_by'               => $this->getBy(),
                        'ipb_by_text'          => $this->getByName(),
-                       'ipb_reason'           => $this->mReason,
                        'ipb_create_account'   => $this->prevents( 'createaccount' ),
                        'ipb_deleted'          => (int)$this->mHideName, // typecast required for SQLite
                        'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
-               ];
+               ] + CommentStore::newKey( 'ipb_reason' )->insert( $dbw, $this->mReason );
        }
 
        /**
@@ -1354,7 +1353,7 @@ class Block {
                                self::TYPE_IP
                        ];
 
-               } elseif ( IP::isValidBlock( $target ) ) {
+               } elseif ( IP::isValidRange( $target ) ) {
                        # Can't create a User from an IP range
                        return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
                }
@@ -1513,7 +1512,7 @@ class Block {
         *
         * @param string $cookieValue The string in which to find the ID.
         *
-        * @return integer|null The block ID, or null if the HMAC is present and invalid.
+        * @return int|null The block ID, or null if the HMAC is present and invalid.
         */
        public static function getIdFromCookieValue( $cookieValue ) {
                // Extract the ID prefix from the cookie value (may be the whole value, if no bang found).
diff --git a/includes/CategoriesRdf.php b/includes/CategoriesRdf.php
new file mode 100644 (file)
index 0000000..e19dc2a
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+use Wikimedia\Purtle\RdfWriter;
+
+/**
+ * Helper class to produce RDF representation of categories.
+ */
+class CategoriesRdf {
+       /**
+        * Prefix used for Mediawiki ontology in the dump.
+        */
+       const ONTOLOGY_PREFIX = 'mediawiki';
+       /**
+        * Base URL for Mediawiki ontology.
+        */
+       const ONTOLOGY_URL = 'https://www.mediawiki.org/ontology#';
+       /**
+        * OWL description of the ontology.
+        */
+       const OWL_URL = 'https://www.mediawiki.org/ontology/ontology.owl';
+       /**
+        * Current version of the dump format.
+        */
+       const FORMAT_VERSION = "1.0";
+       /**
+        * @var RdfWriter
+        */
+       private $rdfWriter;
+
+       public function __construct( RdfWriter $writer ) {
+               $this->rdfWriter = $writer;
+       }
+
+       /**
+        * Setup prefixes relevant for the dump
+        */
+       public function setupPrefixes() {
+               $this->rdfWriter->prefix( self::ONTOLOGY_PREFIX, self::ONTOLOGY_URL );
+               $this->rdfWriter->prefix( 'rdfs', 'http://www.w3.org/2000/01/rdf-schema#' );
+               $this->rdfWriter->prefix( 'owl', 'http://www.w3.org/2002/07/owl#' );
+               $this->rdfWriter->prefix( 'schema', 'http://schema.org/' );
+               $this->rdfWriter->prefix( 'cc', 'http://creativecommons.org/ns#' );
+       }
+
+       /**
+        * Write RDF data for link between categories.
+        * @param string $fromName Child category name
+        * @param string $toName Parent category name
+        */
+       public function writeCategoryLinkData( $fromName, $toName ) {
+               $titleFrom = Title::makeTitle( NS_CATEGORY, $fromName );
+               $titleTo = Title::makeTitle( NS_CATEGORY, $toName );
+               $this->rdfWriter->about( $this->titleToUrl( $titleFrom ) )
+                       ->say( self::ONTOLOGY_PREFIX, 'isInCategory' )
+                       ->is( $this->titleToUrl( $titleTo ) );
+       }
+
+       /**
+        * Write out the data for single category.
+        * @param string $categoryName Category name
+        */
+       public function writeCategoryData( $categoryName ) {
+               $title = Title::makeTitle( NS_CATEGORY, $categoryName );
+               $this->rdfWriter->about( $this->titleToUrl( $title ) )
+                       ->say( 'a' )
+                       ->is( self::ONTOLOGY_PREFIX, 'Category' );
+               $titletext = $title->getText();
+               $this->rdfWriter->say( 'rdfs', 'label' )->value( $titletext );
+       }
+
+       /**
+        * Convert Title to link to target page.
+        * @param Title $title
+        * @return string
+        */
+       private function titleToUrl( Title $title ) {
+               return $title->getFullURL( '', false, PROTO_CANONICAL );
+       }
+}
index c22ea64..629962d 100644 (file)
@@ -48,7 +48,7 @@ class Category {
 
        /**
         * Set up all member variables using a database query.
-        * @param integer $mode
+        * @param int $mode
         * @throws MWException
         * @return bool True on success, false on failure.
         */
diff --git a/includes/CommentStore.php b/includes/CommentStore.php
new file mode 100644 (file)
index 0000000..2ed21d1
--- /dev/null
@@ -0,0 +1,585 @@
+<?php
+/**
+ * Manage storage of comments in the database
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * CommentStore handles storage of comments (edit summaries, log reasons, etc)
+ * in the database.
+ * @since 1.30
+ */
+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:
+        *  - table: Temporary table name
+        *  - pk: Temporary table column referring to the main table's primary key
+        *  - field: Temporary table column referring comment.comment_id
+        *  - joinPK: Main table's primary key
+        */
+       protected static $tempTables = [
+               'rev_comment' => [
+                       'table' => 'revision_comment_temp',
+                       'pk' => 'revcomment_rev',
+                       'field' => 'revcomment_comment_id',
+                       'joinPK' => 'rev_id',
+               ],
+               'img_description' => [
+                       'table' => 'image_comment_temp',
+                       'pk' => 'imgcomment_name',
+                       'field' => 'imgcomment_description_id',
+                       'joinPK' => 'img_name',
+               ],
+       ];
+
+       /**
+        * Fields that formerly used $tempTables
+        * @var array Key is '$key', value is the MediaWiki version in which it was
+        *  removed from $tempTables.
+        */
+       protected static $formerTempTables = [];
+
+       /** @var string */
+       protected $key;
+
+       /** @var int One of the MIGRATION_* constants */
+       protected $stage;
+
+       /** @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, Language $lang = null ) {
+               global $wgCommentTableSchemaMigrationStage, $wgContLang;
+
+               $this->key = $key;
+               $this->stage = $wgCommentTableSchemaMigrationStage;
+               $this->lang = $lang ?: $wgContLang;
+       }
+
+       /**
+        * Static constructor for easier chaining
+        * @param string $key A key such as "rev_comment" identifying the comment
+        *  field being fetched.
+        * @return CommentStore
+        */
+       public static function newKey( $key ) {
+               return new CommentStore( $key );
+       }
+
+       /**
+        * Get SELECT fields for the comment key
+        *
+        * Each resulting row should be passed to `self::getCommentLegacy()` to get the
+        * actual comment.
+        *
+        * @note Use of this method may require a subsequent database query to
+        *  actually fetch the comment. If possible, use `self::getJoin()` instead.
+        * @return string[] to include in the `$vars` to `IDatabase->select()`. All
+        *  fields are aliased, so `+` is safe to use.
+        */
+       public function getFields() {
+               $fields = [];
+               if ( $this->stage === MIGRATION_OLD ) {
+                       $fields["{$this->key}_text"] = $this->key;
+                       $fields["{$this->key}_data"] = 'NULL';
+                       $fields["{$this->key}_cid"] = 'NULL';
+               } else {
+                       if ( $this->stage < MIGRATION_NEW ) {
+                               $fields["{$this->key}_old"] = $this->key;
+                       }
+                       if ( isset( self::$tempTables[$this->key] ) ) {
+                               $fields["{$this->key}_pk"] = self::$tempTables[$this->key]['joinPK'];
+                       } else {
+                               $fields["{$this->key}_id"] = "{$this->key}_id";
+                       }
+               }
+               return $fields;
+       }
+
+       /**
+        * Get SELECT fields and joins for the comment key
+        *
+        * Each resulting row should be passed to `self::getComment()` to get the
+        * actual comment.
+        *
+        * @return array With three keys:
+        *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+        *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
+        *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+        *  All tables, fields, and joins are aliased, so `+` is safe to use.
+        */
+       public function getJoin() {
+               if ( $this->joinCache === null ) {
+                       $tables = [];
+                       $fields = [];
+                       $joins = [];
+
+                       if ( $this->stage === MIGRATION_OLD ) {
+                               $fields["{$this->key}_text"] = $this->key;
+                               $fields["{$this->key}_data"] = 'NULL';
+                               $fields["{$this->key}_cid"] = 'NULL';
+                       } else {
+                               $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
+
+                               if ( isset( self::$tempTables[$this->key] ) ) {
+                                       $t = self::$tempTables[$this->key];
+                                       $alias = "temp_$this->key";
+                                       $tables[$alias] = $t['table'];
+                                       $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+                                       $joinField = "{$alias}.{$t['field']}";
+                               } else {
+                                       $joinField = "{$this->key}_id";
+                               }
+
+                               $alias = "comment_$this->key";
+                               $tables[$alias] = 'comment';
+                               $joins[$alias] = [ $join, "{$alias}.comment_id = {$joinField}" ];
+
+                               if ( $this->stage === MIGRATION_NEW ) {
+                                       $fields["{$this->key}_text"] = "{$alias}.comment_text";
+                               } else {
+                                       $fields["{$this->key}_text"] = "COALESCE( {$alias}.comment_text, $this->key )";
+                               }
+                               $fields["{$this->key}_data"] = "{$alias}.comment_data";
+                               $fields["{$this->key}_cid"] = "{$alias}.comment_id";
+                       }
+
+                       $this->joinCache = [
+                               'tables' => $tables,
+                               'fields' => $fields,
+                               'joins' => $joins,
+                       ];
+               }
+
+               return $this->joinCache;
+       }
+
+       /**
+        * Extract the comment from a row
+        *
+        * Shared implementation for getComment() and getCommentLegacy()
+        *
+        * @param IDatabase|null $db Database handle for getCommentLegacy(), or null for getComment()
+        * @param object|array $row
+        * @param bool $fallback
+        * @return CommentStoreComment
+        */
+       private function getCommentInternal( IDatabase $db = null, $row, $fallback = false ) {
+               $key = $this->key;
+               $row = (array)$row;
+               if ( array_key_exists( "{$key}_text", $row ) && array_key_exists( "{$key}_data", $row ) ) {
+                       $cid = isset( $row["{$key}_cid"] ) ? $row["{$key}_cid"] : null;
+                       $text = $row["{$key}_text"];
+                       $data = $row["{$key}_data"];
+               } elseif ( $this->stage === MIGRATION_OLD ) {
+                       $cid = null;
+                       if ( $fallback && isset( $row[$key] ) ) {
+                               wfLogWarning( "Using deprecated fallback handling for comment $key" );
+                               $text = $row[$key];
+                       } else {
+                               wfLogWarning( "Missing {$key}_text and {$key}_data fields in row with MIGRATION_OLD" );
+                               $text = '';
+                       }
+                       $data = null;
+               } else {
+                       if ( isset( self::$tempTables[$key] ) ) {
+                               if ( array_key_exists( "{$key}_pk", $row ) ) {
+                                       if ( !$db ) {
+                                               throw new InvalidArgumentException(
+                                                       "\$row does not contain fields needed for comment $key and getComment(), but "
+                                                       . "does have fields for getCommentLegacy()"
+                                               );
+                                       }
+                                       $t = self::$tempTables[$key];
+                                       $id = $row["{$key}_pk"];
+                                       $row2 = $db->selectRow(
+                                               [ $t['table'], 'comment' ],
+                                               [ 'comment_id', 'comment_text', 'comment_data' ],
+                                               [ $t['pk'] => $id ],
+                                               __METHOD__,
+                                               [],
+                                               [ 'comment' => [ 'JOIN', [ "comment_id = {$t['field']}" ] ] ]
+                                       );
+                               } elseif ( $fallback && isset( $row[$key] ) ) {
+                                       wfLogWarning( "Using deprecated fallback handling for comment $key" );
+                                       $row2 = (object)[ 'comment_text' => $row[$key], 'comment_data' => null ];
+                               } else {
+                                       throw new InvalidArgumentException( "\$row does not contain fields needed for comment $key" );
+                               }
+                       } else {
+                               if ( array_key_exists( "{$key}_id", $row ) ) {
+                                       if ( !$db ) {
+                                               throw new InvalidArgumentException(
+                                                       "\$row does not contain fields needed for comment $key and getComment(), but "
+                                                       . "does have fields for getCommentLegacy()"
+                                               );
+                                       }
+                                       $id = $row["{$key}_id"];
+                                       $row2 = $db->selectRow(
+                                               'comment',
+                                               [ 'comment_id', 'comment_text', 'comment_data' ],
+                                               [ 'comment_id' => $id ],
+                                               __METHOD__
+                                       );
+                               } elseif ( $fallback && isset( $row[$key] ) ) {
+                                       wfLogWarning( "Using deprecated fallback handling for comment $key" );
+                                       $row2 = (object)[ 'comment_text' => $row[$key], 'comment_data' => null ];
+                               } else {
+                                       throw new InvalidArgumentException( "\$row does not contain fields needed for comment $key" );
+                               }
+                       }
+
+                       if ( $row2 ) {
+                               $cid = $row2->comment_id;
+                               $text = $row2->comment_text;
+                               $data = $row2->comment_data;
+                       } elseif ( $this->stage < MIGRATION_NEW && array_key_exists( "{$key}_old", $row ) ) {
+                               $cid = null;
+                               $text = $row["{$key}_old"];
+                               $data = null;
+                       } else {
+                               // @codeCoverageIgnoreStart
+                               wfLogWarning( "Missing comment row for $key, id=$id" );
+                               $cid = null;
+                               $text = '';
+                               $data = null;
+                               // @codeCoverageIgnoreEnd
+                       }
+               }
+
+               $msg = null;
+               if ( $data !== null ) {
+                       $data = FormatJson::decode( $data );
+                       if ( !is_object( $data ) ) {
+                               // @codeCoverageIgnoreStart
+                               wfLogWarning( "Invalid JSON object in comment: $data" );
+                               $data = null;
+                               // @codeCoverageIgnoreEnd
+                       } else {
+                               $data = (array)$data;
+                               if ( isset( $data['_message'] ) ) {
+                                       $msg = self::decodeMessage( $data['_message'] )
+                                               ->setInterfaceMessageFlag( true );
+                               }
+                               if ( !empty( $data['_null'] ) ) {
+                                       $data = null;
+                               } else {
+                                       foreach ( $data as $k => $v ) {
+                                               if ( substr( $k, 0, 1 ) === '_' ) {
+                                                       unset( $data[$k] );
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               return new CommentStoreComment( $cid, $text, $msg, $data );
+       }
+
+       /**
+        * Extract the comment from a row
+        *
+        * Use `self::getJoin()` to ensure the row contains the needed data.
+        *
+        * If you need to fake a comment in a row for some reason, set fields
+        * `{$key}_text` (string) and `{$key}_data` (JSON string or null).
+        *
+        * @param object|array $row Result row.
+        * @param bool $fallback If true, fall back as well as possible instead of throwing an exception.
+        * @return CommentStoreComment
+        */
+       public function getComment( $row, $fallback = false ) {
+               return $this->getCommentInternal( null, $row, $fallback );
+       }
+
+       /**
+        * Extract the comment from a row, with legacy lookups.
+        *
+        * If `$row` might have been generated using `self::getFields()` rather
+        * than `self::getJoin()`, use this. Prefer `self::getComment()` if you
+        * know callers used `self::getJoin()` for the row fetch.
+        *
+        * If you need to fake a comment in a row for some reason, set fields
+        * `{$key}_text` (string) and `{$key}_data` (JSON string or null).
+        *
+        * @param IDatabase $db Database handle to use for lookup
+        * @param object|array $row Result row.
+        * @param bool $fallback If true, fall back as well as possible instead of throwing an exception.
+        * @return CommentStoreComment
+        */
+       public function getCommentLegacy( IDatabase $db, $row, $fallback = false ) {
+               return $this->getCommentInternal( $db, $row, $fallback );
+       }
+
+       /**
+        * Create a new CommentStoreComment, inserting it into the database if necessary
+        *
+        * If a comment is going to be passed to `self::insert()` or the like
+        * multiple times, it will be more efficient to pass a CommentStoreComment
+        * once rather than making `self::insert()` do it every time through.
+        *
+        * @note When passing a CommentStoreComment, this may set `$comment->id` if
+        *  it's not already set. If `$comment->id` is already set, it will not be
+        *  verified that the specified comment actually exists or that it
+        *  corresponds to the comment text, message, and/or data in the
+        *  CommentStoreComment.
+        * @param IDatabase $dbw Database handle to insert on. Unused if `$comment`
+        *  is a CommentStoreComment and `$comment->id` is set.
+        * @param string|Message|CommentStoreComment $comment Comment text or Message object, or
+        *  a CommentStoreComment.
+        * @param array|null $data Structured data to store. Keys beginning with '_' are reserved.
+        *  Ignored if $comment is a CommentStoreComment.
+        * @return CommentStoreComment
+        */
+       public function createComment( IDatabase $dbw, $comment, array $data = null ) {
+               global $wgContLang;
+
+               if ( !$comment instanceof CommentStoreComment ) {
+                       if ( $data !== null ) {
+                               foreach ( $data as $k => $v ) {
+                                       if ( substr( $k, 0, 1 ) === '_' ) {
+                                               throw new InvalidArgumentException( 'Keys in $data beginning with "_" are reserved' );
+                                       }
+                               }
+                       }
+                       if ( $comment instanceof Message ) {
+                               $message = clone $comment;
+                               $text = $message->inLanguage( $wgContLang ) // Avoid $wgForceUIMsgAsContentMsg
+                                       ->setInterfaceMessageFlag( true )
+                                       ->text();
+                               $comment = new CommentStoreComment( null, $text, $message, $data );
+                       } else {
+                               $comment = new CommentStoreComment( null, $comment, null, $data );
+                       }
+               }
+
+               # 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 ) {
+                               if ( $dbData === null ) {
+                                       $dbData = [ '_null' => true ];
+                               }
+                               $dbData['_message'] = self::encodeMessage( $comment->message );
+                       }
+                       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 );
+                       $comment->id = $dbw->selectField(
+                               'comment',
+                               'comment_id',
+                               [
+                                       'comment_hash' => $hash,
+                                       'comment_text' => $comment->text,
+                                       'comment_data' => $dbData,
+                               ],
+                               __METHOD__
+                       );
+                       if ( !$comment->id ) {
+                               $dbw->insert(
+                                       'comment',
+                                       [
+                                               'comment_hash' => $hash,
+                                               'comment_text' => $comment->text,
+                                               'comment_data' => $dbData,
+                                       ],
+                                       __METHOD__
+                               );
+                               $comment->id = $dbw->insertId();
+                       }
+               }
+
+               return $comment;
+       }
+
+       /**
+        * Implementation for `self::insert()` and `self::insertWithTempTable()`
+        * @param IDatabase $dbw
+        * @param string|Message|CommentStoreComment $comment
+        * @param array|null $data
+        * @return array [ array $fields, callable $callback ]
+        */
+       private function insertInternal( IDatabase $dbw, $comment, $data ) {
+               $fields = [];
+               $callback = null;
+
+               $comment = $this->createComment( $dbw, $comment, $data );
+
+               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+                       $fields[$this->key] = $this->lang->truncate( $comment->text, 255 );
+               }
+
+               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+                       if ( isset( self::$tempTables[$this->key] ) ) {
+                               $t = self::$tempTables[$this->key];
+                               $func = __METHOD__;
+                               $commentId = $comment->id;
+                               $callback = function ( $id ) use ( $dbw, $commentId, $t, $func ) {
+                                       $dbw->insert(
+                                               $t['table'],
+                                               [
+                                                       $t['pk'] => $id,
+                                                       $t['field'] => $commentId,
+                                               ],
+                                               $func
+                                       );
+                               };
+                       } else {
+                               $fields["{$this->key}_id"] = $comment->id;
+                       }
+               }
+
+               return [ $fields, $callback ];
+       }
+
+       /**
+        * 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.
+        * @param IDatabase $dbw Database handle to insert on
+        * @param string|Message|CommentStoreComment $comment As for `self::createComment()`
+        * @param array|null $data As for `self::createComment()`
+        * @return array Fields for the insert or update
+        */
+       public function insert( IDatabase $dbw, $comment, $data = null ) {
+               if ( isset( self::$tempTables[$this->key] ) ) {
+                       throw new InvalidArgumentException( "Must use insertWithTempTable() for $this->key" );
+               }
+
+               list( $fields ) = $this->insertInternal( $dbw, $comment, $data );
+               return $fields;
+       }
+
+       /**
+        * 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.
+        *
+        * @note It's recommended to include both the call to this method and the
+        *  row insert in the same transaction.
+        * @param IDatabase $dbw Database handle to insert on
+        * @param string|Message|CommentStoreComment $comment As for `self::createComment()`
+        * @param array|null $data As for `self::createComment()`
+        * @return array Two values:
+        *  - array Fields for the insert or update
+        *  - callable Function to call when the primary key of the row being
+        *    inserted/updated is known. Pass it that primary key.
+        */
+       public function insertWithTempTable( IDatabase $dbw, $comment, $data = null ) {
+               if ( isset( self::$formerTempTables[$this->key] ) ) {
+                       wfDeprecated( __METHOD__ . " for $this->key", self::$formerTempTables[$this->key] );
+               } elseif ( !isset( self::$tempTables[$this->key] ) ) {
+                       throw new InvalidArgumentException( "Must use insert() for $this->key" );
+               }
+
+               list( $fields, $callback ) = $this->insertInternal( $dbw, $comment, $data );
+               if ( !$callback ) {
+                       $callback = function () {
+                               // Do nothing.
+                       };
+               }
+               return [ $fields, $callback ];
+       }
+
+       /**
+        * Encode a Message as a PHP data structure
+        * @param Message $msg
+        * @return array
+        */
+       protected static function encodeMessage( Message $msg ) {
+               $key = count( $msg->getKeysToTry() ) > 1 ? $msg->getKeysToTry() : $msg->getKey();
+               $params = $msg->getParams();
+               foreach ( $params as &$param ) {
+                       if ( $param instanceof Message ) {
+                               $param = [
+                                       'message' => self::encodeMessage( $param )
+                               ];
+                       }
+               }
+               array_unshift( $params, $key );
+               return $params;
+       }
+
+       /**
+        * Decode a message that was encoded by self::encodeMessage()
+        * @param array $data
+        * @return Message
+        */
+       protected static function decodeMessage( $data ) {
+               $key = array_shift( $data );
+               foreach ( $data as &$param ) {
+                       if ( is_object( $param ) ) {
+                               $param = (array)$param;
+                       }
+                       if ( is_array( $param ) && count( $param ) === 1 && isset( $param['message'] ) ) {
+                               $param = self::decodeMessage( $param['message'] );
+                       }
+               }
+               return new Message( $key, $data );
+       }
+
+       /**
+        * Hashing function for comment storage
+        * @param string $text Comment text
+        * @param string|null $data Comment data
+        * @return int 32-bit signed integer
+        */
+       public static function hash( $text, $data ) {
+               $hash = crc32( $text ) ^ crc32( (string)$data );
+
+               // 64-bit PHP returns an unsigned CRC, change it to signed for
+               // insertion into the database.
+               if ( $hash >= 0x80000000 ) {
+                       $hash |= -1 << 32;
+               }
+
+               return $hash;
+       }
+
+}
diff --git a/includes/CommentStoreComment.php b/includes/CommentStoreComment.php
new file mode 100644 (file)
index 0000000..afc1374
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Value object for CommentStore
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * CommentStoreComment represents a comment stored by CommentStore. The fields
+ * should be considered read-only.
+ * @since 1.30
+ */
+class CommentStoreComment {
+
+       /** @var int|null Comment ID, if any */
+       public $id;
+
+       /** @var string Text version of the comment */
+       public $text;
+
+       /** @var Message Message version of the comment. Might be a RawMessage */
+       public $message;
+
+       /** @var array|null Structured data of the comment */
+       public $data;
+
+       /**
+        * @private For use by CommentStore only
+        * @param int|null $id
+        * @param string $text
+        * @param Message|null $message
+        * @param array|null $data
+        */
+       public function __construct( $id, $text, Message $message = null, array $data = null ) {
+               $this->id = $id;
+               $this->text = $text;
+               $this->message = $message ?: new RawMessage( '$1', [ $text ] );
+               $this->data = $data;
+       }
+}
index 4e162f6..cf8e089 100644 (file)
@@ -755,6 +755,8 @@ $wgCopyUploadProxy = false;
  * timeout longer than the default $wgHTTPTimeout. False means fallback
  * to default.
  *
+ * @var int|bool
+ *
  * @since 1.22
  */
 $wgCopyUploadTimeout = false;
@@ -2053,8 +2055,8 @@ $wgDBmysql5 = false;
 $wgDBOracleDRCP = false;
 
 /**
- * Other wikis on this site, can be administered from a single developer
- * account.
+ * Other wikis on this site, can be administered from a single developer account.
+ *
  * Array numeric key => database name
  */
 $wgLocalDatabases = [];
@@ -3235,14 +3237,6 @@ $wgHTMLFormAllowTableFormat = true;
  */
 $wgUseMediaWikiUIEverywhere = false;
 
-/**
- * Temporary variable that determines whether the EditPage class should use OOjs UI or not.
- * This will be removed later and OOjs UI will become the only option.
- *
- * @since 1.29
- */
-$wgOOUIEditPage = true;
-
 /**
  * Whether to label the store-to-database-and-show-to-others button in the editor
  * as "Save page"/"Save changes" if false (the default) or, if true, instead as
@@ -4901,7 +4895,7 @@ $wgDefaultUserOptions = [
        'date' => 'default',
        'diffonly' => 0,
        'disablemail' => 0,
-       'editfont' => 'default',
+       'editfont' => 'monospace',
        'editondblclick' => 0,
        'editsectiononrightclick' => 0,
        'enotifminoredits' => 0,
@@ -6846,6 +6840,11 @@ $wgStructuredChangeFiltersEnableExperimentalViews = false;
  */
 $wgStructuredChangeFiltersEnableLiveUpdate = false;
 
+/**
+ * Whether to enable RCFilters app on Special:Watchlist
+ */
+$wgStructuredChangeFiltersOnWatchlist = false;
+
 /**
  * Use new page patrolling to check new pages on Special:Newpages
  */
@@ -8274,6 +8273,7 @@ $wgShellLocale = 'C.UTF-8';
 
 /**
  * Timeout for HTTP requests done internally, in seconds.
+ * @var int
  */
 $wgHTTPTimeout = 25;
 
@@ -8303,8 +8303,6 @@ $wgHTTPProxy = false;
  *   subdomain thereof, then no proxy will be used.
  *   Command-line scripts are not affected by this setting and will always use
  *   the proxy if it is configured.
- * - ChronologyProtector: Decide to shutdown LBFactory asynchronously instead
- *   synchronously if the current response redirects to a local virtual host.
  *
  * @since 1.25
  */
@@ -8729,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:
  *
@@ -8765,6 +8775,13 @@ $wgExperiencedUserMemberSince = 30; # days
  */
 $wgInterwikiPrefixDisplayTypes = [];
 
+/**
+ * Comment table schema migration stage.
+ * @since 1.30
+ * @var int One of the MIGRATION_* constants
+ */
+$wgCommentTableSchemaMigrationStage = MIGRATION_OLD;
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 49341c5..06a5cc3 100644 (file)
@@ -336,7 +336,7 @@ class EditPage {
        /** @var string */
        public $edittime = '';
 
-       /** @var integer */
+       /** @var int */
        private $editRevId = null;
 
        /** @var string */
@@ -413,11 +413,6 @@ class EditPage {
         */
        private $isOldRev = false;
 
-       /**
-        * @var bool Whether OOUI should be enabled here
-        */
-       private $oouiEnabled = false;
-
        /**
         * @param Article $article
         */
@@ -431,8 +426,6 @@ class EditPage {
 
                $handler = ContentHandler::getForModelID( $this->contentModel );
                $this->contentFormat = $handler->getDefaultFormat();
-
-               $this->oouiEnabled = $this->context->getConfig()->get( 'OOUIEditPage' );
        }
 
        /**
@@ -485,10 +478,11 @@ class EditPage {
 
        /**
         * Check if the edit page is using OOUI controls
-        * @return bool
+        * @return bool Always true
+        * @deprecated since 1.30
         */
        public function isOouiEnabled() {
-               return $this->oouiEnabled;
+               return true;
        }
 
        /**
@@ -517,6 +511,7 @@ class EditPage {
         * @deprecated since 1.29, call edit directly
         */
        public function submit() {
+               wfDeprecated( __METHOD__, '1.29' );
                $this->edit();
        }
 
@@ -532,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;
@@ -542,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;
                }
 
@@ -707,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;
                }
 
@@ -728,7 +724,7 @@ class EditPage {
 
                $this->displayViewSourcePage(
                        $content,
-                       $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' )
+                       $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
                );
        }
 
@@ -738,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 );
@@ -769,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 );
                }
        }
 
@@ -856,10 +851,7 @@ class EditPage {
         * @throws ErrorPageError
         */
        public function importFormData( &$request ) {
-               global $wgContLang, $wgUser;
-
-               # Allow users to change the mode for testing
-               $this->oouiEnabled = $request->getFuzzyBool( 'ooui', $this->oouiEnabled );
+               global $wgUser;
 
                # Section edit can come from either the form or a link
                $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
@@ -885,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
@@ -898,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' );
@@ -1132,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;
 
@@ -1240,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 );
                                }
 
@@ -1469,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 );
        }
 
@@ -1515,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
@@ -1532,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 ) {
@@ -1555,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:
@@ -1568,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:
@@ -1596,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:
@@ -1726,7 +1720,7 @@ class EditPage {
         *   - spam (string): Spam string from content if any spam is detected by
         *     matchSpamRegex.
         *   - sectionanchor (string): Section anchor for a section save.
-        *   - nullEdit (boolean): Set if doEditContent is OK.  True if null edit,
+        *   - nullEdit (bool): Set if doEditContent is OK.  True if null edit,
         *     false otherwise.
         *   - redirect (bool): Set if doEditContent is OK. True if resulting
         *     revision is a redirect.
@@ -2351,29 +2345,31 @@ class EditPage {
        }
 
        public function setHeaders() {
-               global $wgOut, $wgUser, $wgAjaxEditStash;
+               global $wgUser, $wgAjaxEditStash;
+
+               $out = $this->context->getOutput();
 
-               $wgOut->addModules( 'mediawiki.action.edit' );
-               $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
+               $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 ) {
@@ -2396,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,
                ] );
@@ -2409,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 )
@@ -2426,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' );
                                }
                        }
@@ -2438,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
                                                ] );
                                        }
@@ -2458,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(),
                                        '',
@@ -2484,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>",
                                        [
@@ -2493,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>",
                                        [
@@ -2507,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,
@@ -2528,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
                                );
@@ -2602,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.
         *
@@ -2610,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
@@ -2621,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();
 
@@ -2636,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() ) {
@@ -2657,17 +2655,17 @@ 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' => $this->oouiEnabled ? 'mw-editform-ooui' : 'mw-editform-legacy',
+                               'class' => 'mw-editform',
                                'id' => self::EDITFORM_ID,
                                'name' => self::EDITFORM_ID,
                                'method' => 'post',
@@ -2678,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',
@@ -2703,21 +2701,21 @@ 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();
 
                if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
                        $username = $this->lastDelete->user_name;
-                       $comment = $this->lastDelete->log_comment;
+                       $comment = CommentStore::newKey( 'log_comment' )->getComment( $this->lastDelete )->text;
 
                        // It is better to not parse the comment at all than to have templates expanded in the middle
                        // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
                        $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,
@@ -2729,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
@@ -2740,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 ) {
@@ -2759,35 +2757,29 @@ class EditPage {
                }
 
                $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
-               $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
-
-               $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
-               $wgOut->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
+               $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
 
-               $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
-               $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
+               $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+               $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
 
-               // Preserve &ooui=1 / &ooui=0 from URL parameters after submitting the page for preview
-               $wgOut->addHTML( Html::hidden( 'ooui', $this->oouiEnabled ? '1' : '0' ) );
+               $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
+               $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
 
-               // following functions will need OOUI, enable it only once; here.
-               if ( $this->oouiEnabled ) {
-                       $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 ) {
@@ -2805,7 +2797,7 @@ class EditPage {
                        $this->showContentForm();
                }
 
-               $wgOut->addHTML( $this->editFormTextAfterContent );
+               $out->addHTML( $this->editFormTextAfterContent );
 
                $this->showStandardInputs();
 
@@ -2815,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 {
@@ -2838,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>' );
                        }
                }
 
@@ -2852,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 );
@@ -2906,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' ) {
@@ -2925,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' ) {
@@ -2970,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'
                                                );
@@ -2983,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
@@ -3016,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'
                                );
                        }
@@ -3024,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' ]
                                                        );
@@ -3086,16 +3079,17 @@ class EditPage {
         * inferred by the id given to the input. You can remove them both by
         * passing [ 'id' => false ] to $userInputAttrs.
         *
+        * @deprecated since 1.30 Use getSummaryInputWidget() instead
         * @param string $summary The value of the summary input
         * @param string $labelText The html to place inside the label
         * @param array $inputAttrs Array of attrs to use on the input
         * @param array $spanLabelAttrs Array of attrs to use on the span inside the label
-        *
         * @return array An array in the format [ $label, $input ]
         */
        public function getSummaryInput( $summary = "", $labelText = null,
                $inputAttrs = null, $spanLabelAttrs = null
        ) {
+               wfDeprecated( __METHOD__, '1.30' );
                $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
                $inputAttrs += Linker::tooltipAndAccesskeyAttribs( 'summary' );
 
@@ -3120,9 +3114,9 @@ class EditPage {
        }
 
        /**
-        * Same as self::getSummaryInput, but uses OOUI, instead of plain HTML.
         * Builds a standard summary input with a label.
         *
+        * @deprecated since 1.30 Use getSummaryInputWidget() instead
         * @param string $summary The value of the summary input
         * @param string $labelText The html to place inside the label
         * @param array $inputAttrs Array of attrs to use on the input
@@ -3130,6 +3124,20 @@ class EditPage {
         * @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
         */
        function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
+               wfDeprecated( __METHOD__, '1.30' );
+               $this->getSummaryInputWidget( $summary, $labelText, $inputAttrs );
+       }
+
+       /**
+        * Builds a standard summary input with a label.
+        *
+        * @param string $summary The value of the summary input
+        * @param string $labelText The html to place inside the label
+        * @param array $inputAttrs Array of attrs to use on the input
+        *
+        * @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
+        */
+       function getSummaryInputWidget( $summary = "", $labelText = null, $inputAttrs = null ) {
                $inputAttrs = OOUI\Element::configFromHtmlAttributes(
                        $this->getSummaryInputAttributes( $inputAttrs )
                );
@@ -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,20 +3184,11 @@ class EditPage {
                }
 
                $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
-               if ( $this->oouiEnabled ) {
-                       $wgOut->addHTML( $this->getSummaryInputOOUI(
+               $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
                                $summary,
                                $labelText,
                                [ 'class' => $summaryClass ]
                        ) );
-               } else {
-                       list( $label, $input ) = $this->getSummaryInput(
-                               $summary,
-                               $labelText,
-                               [ 'class' => $summaryClass ]
-                       );
-                       $wgOut->addHTML( "{$label} {$input}" );
-               }
        }
 
        /**
@@ -3224,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 ) );
+               $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
@@ -3251,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"
+               );
        }
 
        /**
@@ -3325,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';
@@ -3348,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 );
@@ -3357,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 {
@@ -3372,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();
                }
@@ -3405,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
@@ -3460,7 +3458,7 @@ class EditPage {
                        $difftext = '';
                }
 
-               $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
+               $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
        }
 
        /**
@@ -3469,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' );
                }
        }
@@ -3487,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>' );
                }
        }
 
@@ -3499,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>' );
        }
@@ -3594,36 +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 ) );
                }
 
-               if ( $this->oouiEnabled ) {
-                       $checkboxes = $this->getCheckboxesOOUI(
-                               $tabindex,
-                               [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
-                       );
-                       $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
-               } else {
-                       $checkboxes = $this->getCheckboxes(
-                               $tabindex,
-                               [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
-                       );
-                       $checkboxesHTML = implode( $checkboxes, "\n" );
-               }
+               $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 !== '' ) {
@@ -3643,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" );
        }
 
        /**
@@ -3657,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 );
@@ -3677,7 +3664,7 @@ class EditPage {
                                $this->context->msg( 'storedversion' )->text()
                        );
 
-                       $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+                       $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
                        $this->showTextbox2();
                }
        }
@@ -3704,23 +3691,15 @@ class EditPage {
                } elseif ( $this->getContextTitle()->isRedirect() ) {
                        $cancelParams['redirect'] = 'no';
                }
-               if ( $this->oouiEnabled ) {
-                       return new OOUI\ButtonWidget( [
-                               'id' => 'mw-editform-cancel',
-                               'href' => $this->getContextTitle()->getLinkUrl( $cancelParams ),
-                               'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
-                               'framed' => false,
-                               'infusable' => true,
-                               'flags' => 'destructive',
-                       ] );
-               } else {
-                       return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
-                               $this->getContextTitle(),
-                               new HtmlArmor( $this->context->msg( 'cancel' )->parse() ),
-                               Html::buttonAttributes( [ 'id' => 'mw-editform-cancel' ], [ 'mw-ui-quiet' ] ),
-                               $cancelParams
-                       );
-               }
+
+               return new OOUI\ButtonWidget( [
+                       'id' => 'mw-editform-cancel',
+                       'href' => $this->getContextTitle()->getLinkUrl( $cancelParams ),
+                       'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
+                       'framed' => false,
+                       'infusable' => true,
+                       'flags' => 'destructive',
+               ] );
        }
 
        /**
@@ -3768,8 +3747,9 @@ class EditPage {
         */
        protected function getLastDelete() {
                $dbr = wfGetDB( DB_REPLICA );
+               $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
                $data = $dbr->selectRow(
-                       [ 'logging', 'user' ],
+                       [ 'logging', 'user' ] + $commentQuery['tables'],
                        [
                                'log_type',
                                'log_action',
@@ -3777,11 +3757,10 @@ class EditPage {
                                'log_user',
                                'log_namespace',
                                'log_title',
-                               'log_comment',
                                'log_params',
                                'log_deleted',
                                'user_name'
-                       ], [
+                       ] + $commentQuery['fields'], [
                                'log_namespace' => $this->mTitle->getNamespace(),
                                'log_title' => $this->mTitle->getDBkey(),
                                'log_type' => 'delete',
@@ -3789,7 +3768,10 @@ class EditPage {
                                'user_id=log_user'
                        ],
                        __METHOD__,
-                       [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ]
+                       [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
+                       [
+                               'user' => [ 'JOIN', 'user_id=log_user' ],
+                       ] + $commentQuery['joins']
                );
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
@@ -3798,7 +3780,8 @@ class EditPage {
                        }
 
                        if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
-                               $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
+                               $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
+                               $data->log_comment_data = null;
                        }
                }
 
@@ -3811,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.
@@ -3822,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 );
                        }
@@ -3910,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() );
@@ -3936,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(),
@@ -4163,7 +4148,7 @@ class EditPage {
         *   where bool indicates the checked status of the checkbox
         * @return array
         */
-       protected function getCheckboxesDefinition( $checked ) {
+       public function getCheckboxesDefinition( $checked ) {
                global $wgUser;
                $checkboxes = [];
 
@@ -4202,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
@@ -4257,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;
 
@@ -4284,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( [
@@ -4310,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 ) ] );
@@ -4358,74 +4366,56 @@ class EditPage {
                        'name' => 'wpSave',
                        'tabindex' => ++$tabindex,
                ];
-               if ( $this->oouiEnabled ) {
-                       $saveConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
-                       $buttons['save'] = new OOUI\ButtonInputWidget( [
-                               'id' => 'wpSaveWidget',
-                               'inputId' => 'wpSave',
-                               // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
-                               'useInputTag' => true,
-                               'flags' => [ 'constructive', 'primary' ],
-                               'label' => $buttonLabel,
-                               'infusable' => true,
-                               'type' => 'submit',
-                               'title' => Linker::titleAttrib( 'save' ),
-                               'accessKey' => Linker::accesskey( 'save' ),
-                       ] + $saveConfig );
-               } else {
-                       $buttons['save'] = Html::submitButton(
-                               $buttonLabel,
-                               $attribs + Linker::tooltipAndAccesskeyAttribs( 'save' ) + [ 'id' => 'wpSave' ],
-                               [ 'mw-ui-progressive' ]
-                       );
-               }
+
+               $saveConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
+               $buttons['save'] = new OOUI\ButtonInputWidget( [
+                       'id' => 'wpSaveWidget',
+                       'inputId' => 'wpSave',
+                       // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
+                       'useInputTag' => true,
+                       'flags' => [ 'constructive', 'primary' ],
+                       'label' => $buttonLabel,
+                       'infusable' => true,
+                       'type' => 'submit',
+                       'title' => Linker::titleAttrib( 'save' ),
+                       'accessKey' => Linker::accesskey( 'save' ),
+               ] + $saveConfig );
 
                $attribs = [
                        'name' => 'wpPreview',
                        'tabindex' => ++$tabindex,
                ];
-               if ( $this->oouiEnabled ) {
-                       $previewConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
-                       $buttons['preview'] = new OOUI\ButtonInputWidget( [
-                               'id' => 'wpPreviewWidget',
-                               'inputId' => 'wpPreview',
-                               // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
-                               'useInputTag' => true,
-                               'label' => $this->context->msg( 'showpreview' )->text(),
-                               'infusable' => true,
-                               'type' => 'submit',
-                               'title' => Linker::titleAttrib( 'preview' ),
-                               'accessKey' => Linker::accesskey( 'preview' ),
-                       ] + $previewConfig );
-               } else {
-                       $buttons['preview'] = Html::submitButton(
-                               $this->context->msg( 'showpreview' )->text(),
-                               $attribs + Linker::tooltipAndAccesskeyAttribs( 'preview' ) + [ 'id' => 'wpPreview' ]
-                       );
-               }
+
+               $previewConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
+               $buttons['preview'] = new OOUI\ButtonInputWidget( [
+                       'id' => 'wpPreviewWidget',
+                       'inputId' => 'wpPreview',
+                       // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
+                       'useInputTag' => true,
+                       'label' => $this->context->msg( 'showpreview' )->text(),
+                       'infusable' => true,
+                       'type' => 'submit',
+                       'title' => Linker::titleAttrib( 'preview' ),
+                       'accessKey' => Linker::accesskey( 'preview' ),
+               ] + $previewConfig );
+
                $attribs = [
                        'name' => 'wpDiff',
                        'tabindex' => ++$tabindex,
                ];
-               if ( $this->oouiEnabled ) {
-                       $diffConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
-                       $buttons['diff'] = new OOUI\ButtonInputWidget( [
-                               'id' => 'wpDiffWidget',
-                               'inputId' => 'wpDiff',
-                               // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
-                               'useInputTag' => true,
-                               'label' => $this->context->msg( 'showdiff' )->text(),
-                               'infusable' => true,
-                               'type' => 'submit',
-                               'title' => Linker::titleAttrib( 'diff' ),
-                               'accessKey' => Linker::accesskey( 'diff' ),
-                       ] + $diffConfig );
-               } else {
-                       $buttons['diff'] = Html::submitButton(
-                               $this->context->msg( 'showdiff' )->text(),
-                               $attribs + Linker::tooltipAndAccesskeyAttribs( 'diff' ) + [ 'id' => 'wpDiff' ]
-                       );
-               }
+
+               $diffConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
+               $buttons['diff'] = new OOUI\ButtonInputWidget( [
+                       'id' => 'wpDiffWidget',
+                       'inputId' => 'wpDiff',
+                       // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
+                       'useInputTag' => true,
+                       'label' => $this->context->msg( 'showdiff' )->text(),
+                       'infusable' => true,
+                       'type' => 'submit',
+                       'title' => Linker::titleAttrib( 'diff' ),
+                       'accessKey' => Linker::accesskey( 'diff' ),
+               ] + $diffConfig );
 
                // Avoid PHP 7.1 warning of passing $this by reference
                $editPage = $this;
@@ -4439,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 );
        }
 
        /**
@@ -4459,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' ] );
        }
 
        /**
@@ -4621,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>'
@@