Merge "Move section ID fallbacks into headers themselves"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 31 Aug 2017 21:02:12 +0000 (21:02 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 31 Aug 2017 21:02:12 +0000 (21:02 +0000)
715 files changed:
.gitattributes
.gitignore
.mailmap
.stylelintrc [deleted file]
.stylelintrc.json [new file with mode: 0644]
CREDITS
HISTORY
RELEASE-NOTES-1.30
autoload.php
composer.json
docs/database.txt
docs/ontology.owl [new file with mode: 0644]
docs/uidesign/child-selector-emu.html [deleted file]
includes/AuthPlugin.php
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/ConfiguredReadOnlyMode.php [new file with mode: 0644]
includes/DefaultSettings.php
includes/EditPage.php
includes/FauxRequest.php
includes/FeedUtils.php
includes/FileDeleteForm.php
includes/GlobalFunctions.php
includes/Licenses.php
includes/Linker.php
includes/MagicWord.php
includes/MagicWordArray.php
includes/MediaWiki.php
includes/MergeHistory.php
includes/Message.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/ProtectionForm.php
includes/RawMessage.php [new file with mode: 0644]
includes/ReadOnlyMode.php
includes/Revision.php
includes/RevisionList.php
includes/Sanitizer.php
includes/SiteConfiguration.php
includes/SiteStats.php
includes/StreamFile.php
includes/StubObject.php
includes/Title.php
includes/TrackingCategories.php
includes/WatchedItemQueryService.php
includes/WebStart.php
includes/WikiMap.php
includes/api/ApiAuthManagerHelper.php
includes/api/ApiBase.php
includes/api/ApiCSPReport.php
includes/api/ApiDelete.php
includes/api/ApiHelp.php
includes/api/ApiMain.php
includes/api/ApiMove.php
includes/api/ApiParamInfo.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryFilearchive.php
includes/api/ApiQueryImageInfo.php
includes/api/ApiQueryLinks.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryProtectedTitles.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQuerySearch.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiQueryUsers.php
includes/api/ApiQueryWatchlist.php
includes/api/ApiUsageException.php
includes/api/SearchApi.php
includes/api/i18n/ar.json
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/he.json
includes/api/i18n/it.json
includes/api/i18n/ko.json
includes/api/i18n/lb.json
includes/api/i18n/nb.json
includes/api/i18n/nl.json
includes/api/i18n/pt.json
includes/api/i18n/qqq.json
includes/api/i18n/ru.json
includes/api/i18n/zh-hans.json
includes/api/i18n/zh-hant.json
includes/auth/AbstractAuthenticationProvider.php
includes/auth/AbstractPrimaryAuthenticationProvider.php
includes/auth/AbstractSecondaryAuthenticationProvider.php
includes/auth/AuthManager.php
includes/auth/CreateFromLoginAuthenticationRequest.php
includes/auth/PrimaryAuthenticationProvider.php
includes/cache/HTMLFileCache.php
includes/cache/LinkBatch.php
includes/cache/MessageCache.php
includes/cache/localisation/LocalisationCache.php
includes/changes/ChangesFeed.php
includes/changes/ChangesList.php
includes/changes/ChangesListBooleanFilter.php
includes/changes/ChangesListBooleanFilterGroup.php
includes/changes/ChangesListFilterGroup.php
includes/changes/ChangesListStringOptionsFilter.php
includes/changes/ChangesListStringOptionsFilterGroup.php
includes/changes/EnhancedChangesList.php
includes/changes/OldChangesList.php
includes/changes/RecentChange.php
includes/changetags/ChangeTags.php
includes/collation/CustomUppercaseCollation.php
includes/collation/IcuCollation.php
includes/collation/NumericUppercaseCollation.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/compat/normal/UtfNormal.php
includes/config/EtcdConfig.php
includes/content/ContentHandler.php
includes/content/JsonContent.php
includes/content/TextContent.php
includes/content/TextContentHandler.php
includes/context/ContextSource.php
includes/context/RequestContext.php
includes/dao/DBAccessObjectUtils.php
includes/dao/IDBAccessObject.php
includes/db/MWLBFactory.php
includes/db/ORAResult.php
includes/debug/logger/LegacyLogger.php
includes/debug/logger/monolog/BufferHandler.php
includes/debug/logger/monolog/KafkaHandler.php
includes/debug/logger/monolog/LineFormatter.php
includes/debug/logger/monolog/WikiProcessor.php
includes/deferred/CdnCacheUpdate.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksDeletionUpdate.php
includes/deferred/LinksUpdate.php
includes/deferred/SiteStatsUpdate.php
includes/deferred/WANCacheReapUpdate.php
includes/diff/DiffFormatter.php
includes/diff/WordLevelDiff.php
includes/exception/MWExceptionHandler.php
includes/exception/MWExceptionRenderer.php
includes/export/DumpFilter.php
includes/export/DumpNamespaceFilter.php
includes/export/WikiExporter.php
includes/export/XmlDumpWriter.php
includes/filebackend/filejournal/DBFileJournal.php
includes/filerepo/FileBackendDBRepoWrapper.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignAPIRepo.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/File.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/filerepo/file/UnregisteredLocalFile.php
includes/gallery/TraditionalImageGallery.php
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLFormFieldWithButton.php
includes/http/MWHttpRequest.php
includes/import/UploadSourceAdapter.php
includes/import/WikiImporter.php
includes/import/WikiRevision.php
includes/installer/CliInstaller.php
includes/installer/DatabaseUpdater.php
includes/installer/Installer.php
includes/installer/MysqlUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteInstaller.php
includes/installer/SqliteUpdater.php
includes/installer/WebInstallerOptions.php
includes/installer/i18n/ar.json
includes/installer/i18n/be-tarask.json
includes/installer/i18n/bn.json
includes/installer/i18n/cs.json
includes/installer/i18n/de.json
includes/installer/i18n/el.json
includes/installer/i18n/en.json
includes/installer/i18n/es.json
includes/installer/i18n/fa.json
includes/installer/i18n/fr.json
includes/installer/i18n/gl.json
includes/installer/i18n/he.json
includes/installer/i18n/hi.json
includes/installer/i18n/ko.json
includes/installer/i18n/lb.json
includes/installer/i18n/mk.json
includes/installer/i18n/nb.json
includes/installer/i18n/nl.json
includes/installer/i18n/pt.json
includes/installer/i18n/qqq.json
includes/installer/i18n/ru.json
includes/installer/i18n/sv.json
includes/installer/i18n/uk.json
includes/installer/i18n/zh-hans.json
includes/jobqueue/JobQueueDB.php
includes/jobqueue/JobQueueFederated.php
includes/jobqueue/JobQueueGroup.php
includes/jobqueue/JobRunner.php
includes/jobqueue/jobs/CategoryMembershipChangeJob.php
includes/jobqueue/jobs/HTMLCacheUpdateJob.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/libs/ArrayUtils.php
includes/libs/GenericArrayObject.php
includes/libs/HashRing.php
includes/libs/IEUrlExtension.php
includes/libs/IP.php
includes/libs/MemoizedCallable.php
includes/libs/MultiHttpClient.php
includes/libs/StatusValue.php
includes/libs/composer/ComposerInstalled.php
includes/libs/composer/ComposerLock.php
includes/libs/filebackend/FileBackend.php
includes/libs/filebackend/HTTPFileStreamer.php
includes/libs/filebackend/SwiftFileBackend.php
includes/libs/filebackend/fileop/FileOp.php
includes/libs/lockmanager/ScopedLock.php
includes/libs/mime/IEContentAnalyzer.php
includes/libs/mime/XmlTypeCheck.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/DBConnRef.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/DatabaseSqlite.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/position/MySQLMasterPos.php
includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php
includes/libs/rdbms/encasing/Blob.php
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/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadmonitor/ILoadMonitor.php
includes/libs/rdbms/loadmonitor/LoadMonitor.php
includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php
includes/libs/rdbms/loadmonitor/LoadMonitorNull.php
includes/libs/redis/RedisConnectionPool.php
includes/libs/stats/BufferingStatsdDataFactory.php
includes/libs/stats/IBufferingStatsdDataFactory.php
includes/libs/stats/NullStatsdDataFactory.php
includes/libs/stats/SamplingStatsdClient.php
includes/logging/LogEntry.php
includes/logging/LogEventsList.php
includes/logging/LogPage.php
includes/mail/UserMailer.php
includes/media/Bitmap.php
includes/media/Bitmap_ClientOnly.php
includes/media/FormatMetadata.php
includes/media/ImageHandler.php
includes/media/Jpeg.php
includes/media/MediaHandler.php
includes/media/SVG.php
includes/media/TransformationalImageHandler.php
includes/media/WebP.php
includes/objectcache/SqlBagOStuff.php
includes/page/Article.php
includes/page/ImagePage.php
includes/page/PageArchive.php
includes/page/WikiPage.php
includes/parser/CoreParserFunctions.php
includes/parser/LinkHolderArray.php
includes/parser/Parser.php
includes/parser/ParserOptions.php
includes/parser/ParserOutput.php
includes/parser/Preprocessor_DOM.php
includes/parser/Preprocessor_Hash.php
includes/profiler/ProfileSection.php
includes/profiler/Profiler.php
includes/profiler/SectionProfiler.php
includes/rcfeed/IRCColourfulRCFeedFormatter.php
includes/resourceloader/ResourceLoader.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderJqueryMsgModule.php
includes/resourceloader/ResourceLoaderMediaWikiUtilModule.php
includes/resourceloader/ResourceLoaderModule.php
includes/revisiondelete/RevDelItem.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevDelLogItem.php
includes/revisiondelete/RevDelLogList.php
includes/revisiondelete/RevisionDeleter.php
includes/search/NullIndexField.php
includes/search/SearchEngine.php
includes/search/SearchExactMatchRescorer.php
includes/search/SearchHighlighter.php
includes/search/SearchIndexField.php
includes/search/SearchIndexFieldDefinition.php
includes/search/SearchMySQL.php
includes/search/SearchResult.php
includes/search/SearchSqlite.php
includes/search/SearchSuggestion.php
includes/services/ServiceContainer.php
includes/session/CookieSessionProvider.php
includes/session/PHPSessionHandler.php
includes/session/SessionProvider.php
includes/session/Token.php
includes/site/Site.php
includes/site/SiteImporter.php
includes/skins/BaseTemplate.php
includes/skins/QuickTemplate.php
includes/skins/Skin.php
includes/skins/SkinTemplate.php
includes/specialpage/AuthManagerSpecialPage.php
includes/specialpage/ChangesListSpecialPage.php
includes/specialpage/LoginSignupSpecialPage.php
includes/specialpage/RedirectSpecialPage.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialBlock.php
includes/specials/SpecialBrokenRedirects.php
includes/specials/SpecialContributions.php
includes/specials/SpecialDiff.php
includes/specials/SpecialDoubleRedirects.php
includes/specials/SpecialExport.php
includes/specials/SpecialFilepath.php
includes/specials/SpecialImport.php
includes/specials/SpecialListredirects.php
includes/specials/SpecialMediaStatistics.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialPageLanguage.php
includes/specials/SpecialPermanentLink.php
includes/specials/SpecialPreferences.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialRecentchangeslinked.php
includes/specials/SpecialRevisiondelete.php
includes/specials/SpecialSearch.php
includes/specials/SpecialUncategorizedcategories.php
includes/specials/SpecialWatchlist.php
includes/specials/helpers/LoginHelper.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/specials/pagers/UsersPager.php
includes/templates/EnhancedChangesListGroup.mustache
includes/tidy/Balancer.php
includes/tidy/RemexCompatMunger.php
includes/upload/UploadBase.php
includes/upload/UploadFromChunks.php
includes/upload/UploadFromFile.php
includes/upload/UploadFromStash.php
includes/upload/UploadFromUrl.php
includes/user/User.php
includes/utils/AutoloadGenerator.php
includes/utils/BatchRowIterator.php
includes/utils/BatchRowUpdate.php
includes/utils/BatchRowWriter.php
includes/utils/UIDGenerator.php
includes/widget/SearchInputWidget.php
includes/widget/SelectWithInputWidget.php
includes/widget/search/InterwikiSearchResultSetWidget.php
includes/widget/search/InterwikiSearchResultWidget.php
languages/Language.php
languages/LanguageCode.php
languages/classes/LanguageEn.php
languages/classes/LanguageKk.php
languages/classes/LanguageSr.php
languages/data/Names.php
languages/i18n/af.json
languages/i18n/ais.json [new file with mode: 0644]
languages/i18n/ar.json
languages/i18n/ast.json
languages/i18n/awa.json
languages/i18n/az.json
languages/i18n/ba.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/bho.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/cv.json
languages/i18n/cy.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/frr.json
languages/i18n/gd.json
languages/i18n/gl.json
languages/i18n/got.json
languages/i18n/gu.json
languages/i18n/ha.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/it.json
languages/i18n/ja.json
languages/i18n/jam.json
languages/i18n/jv.json
languages/i18n/kab.json
languages/i18n/khw.json
languages/i18n/km.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/li.json
languages/i18n/lki.json
languages/i18n/lt.json
languages/i18n/lv.json
languages/i18n/lzh.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/mr.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/sa.json
languages/i18n/sd.json
languages/i18n/shi.json
languages/i18n/shn.json
languages/i18n/sk.json
languages/i18n/skr-arab.json
languages/i18n/sl.json
languages/i18n/sq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/su.json
languages/i18n/sv.json
languages/i18n/tay.json [new file with mode: 0644]
languages/i18n/te.json
languages/i18n/th.json
languages/i18n/tl.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/ug-arab.json
languages/i18n/uk.json
languages/i18n/vi.json
languages/i18n/vro.json
languages/i18n/wuu.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
languages/messages/MessagesKm.php
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-ip_changes.sql [new file with mode: 0644]
maintenance/archives/patch-iwlinks-fix-pk.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-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/deleteRevision.php [deleted file]
maintenance/doMaintenance.php
maintenance/dumpCategoriesAsRdf.php [new file with mode: 0644]
maintenance/hhvm/makeRepo.php
maintenance/importDump.php
maintenance/migrateComments.php [new file with mode: 0644]
maintenance/namespaceDupes.php
maintenance/orphans.php
maintenance/postgres/archives/patch-comment-table.sql [new file with mode: 0644]
maintenance/postgres/tables.sql
maintenance/rebuildrecentchanges.php
maintenance/refreshLinks.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-ip_changes.sql [new file with mode: 0644]
maintenance/sqlite/archives/patch-iwlinks-fix-pk.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-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
profileinfo.php
resources/Resources.php
resources/lib/html5shiv/html5shiv.js [new file with mode: 0644]
resources/lib/html5shiv/html5shiv.min.js [new file with mode: 0644]
resources/lib/jquery/jquery.migrate.js
resources/lib/oojs-ui/i18n/en.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/li.json
resources/lib/oojs-ui/i18n/nn.json
resources/lib/oojs-ui/i18n/qqq.json
resources/lib/oojs-ui/i18n/skr-arab.json [new file with mode: 0644]
resources/lib/oojs-ui/i18n/yue.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/wikimedia-ui-base.less [new file with mode: 0644]
resources/src/jquery/jquery.badge.css
resources/src/jquery/jquery.mwExtension.js [deleted file]
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.legacy/wikibits.js
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.FilterGroup.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.skinning/interface.css
resources/src/mediawiki.special/mediawiki.special.apisandbox.css
resources/src/mediawiki.special/mediawiki.special.search.interwikiwidget.styles.less
resources/src/mediawiki.special/mediawiki.special.watchlist.css [new file with mode: 0644]
resources/src/mediawiki.special/mediawiki.special.watchlist.js
resources/src/mediawiki.toolbar/images/ksh/LICENSE
resources/src/mediawiki.toolbar/toolbar.js
resources/src/mediawiki.ui/components/checkbox.less
resources/src/mediawiki.ui/components/icons.less
resources/src/mediawiki.ui/components/images/checkbox-checked.png [new file with mode: 0644]
resources/src/mediawiki.ui/components/images/checkbox-checked.svg [new file with mode: 0644]
resources/src/mediawiki.ui/components/images/checked_disabled.png [deleted file]
resources/src/mediawiki.ui/components/images/checked_disabled.svg [deleted file]
resources/src/mediawiki.ui/components/images/radio_checked.png [deleted file]
resources/src/mediawiki.ui/components/images/radio_checked.svg [deleted file]
resources/src/mediawiki.ui/components/images/radio_disabled.png [deleted file]
resources/src/mediawiki.ui/components/images/radio_disabled.svg [deleted file]
resources/src/mediawiki.ui/components/radio.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/htmlform/styles.css
resources/src/mediawiki/mediawiki.apihelp.css
resources/src/mediawiki/mediawiki.toc.print.css
resources/src/mediawiki/mediawiki.util.js
resources/src/mediawiki/page/watch.js
resources/src/oojs-ui-local.css
tests/common/TestsAutoLoader.php
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/data/media/say-test-mpeg1.mp3 [new file with mode: 0644]
tests/phpunit/data/media/say-test-mpeg2.5.mp3 [new file with mode: 0644]
tests/phpunit/data/media/say-test-mpeg2.mp3 [new file with mode: 0644]
tests/phpunit/data/media/say-test-with-id3.mp3 [new file with mode: 0644]
tests/phpunit/includes/CommentStoreTest.php [new file with mode: 0644]
tests/phpunit/includes/GlobalFunctions/wfThumbIsStandardTest.php
tests/phpunit/includes/RevisionStorageTest.php
tests/phpunit/includes/WatchedItemQueryServiceUnitTest.php
tests/phpunit/includes/WatchedItemStoreUnitTest.php
tests/phpunit/includes/WikiMapTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/auth/LegacyHookPreAuthenticationProviderTest.php
tests/phpunit/includes/changes/ChangesListFilterTest.php
tests/phpunit/includes/changes/EnhancedChangesListTest.php
tests/phpunit/includes/changes/OldChangesListTest.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/content/WikitextStructureTest.php
tests/phpunit/includes/db/LBFactoryTest.php
tests/phpunit/includes/db/LoadBalancerTest.php [new file with mode: 0644]
tests/phpunit/includes/deferred/LinksUpdateTest.php
tests/phpunit/includes/interwiki/ClassicInterwikiLookupTest.php
tests/phpunit/includes/libs/IPTest.php
tests/phpunit/includes/libs/mime/MimeAnalyzerTest.php
tests/phpunit/includes/libs/objectcache/WANObjectCacheTest.php
tests/phpunit/includes/libs/xmp/XMPTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.php
tests/phpunit/includes/media/FakeDimensionFile.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderModuleTest.php
tests/phpunit/includes/resourceloader/ResourceLoaderTest.php
tests/phpunit/includes/search/SearchIndexFieldTest.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php
tests/phpunit/includes/specials/SpecialShortpagesTest.php
tests/phpunit/includes/specials/SpecialWatchlistTest.php
tests/phpunit/languages/LanguageCodeTest.php
tests/phpunit/maintenance/categoriesRdfTest.php [new file with mode: 0644]
tests/phpunit/mocks/MockWebRequest.php
tests/phpunit/mocks/media/MockMediaHandlerFactory.php [deleted file]
tests/phpunit/mocks/media/MockOggHandler.php [deleted file]
tests/phpunit/structure/ApiDocumentationTest.php [deleted file]
tests/phpunit/structure/ApiStructureTest.php [new file with mode: 0644]
tests/phpunit/suites/ParserTestTopLevelSuite.php
tests/qunit/QUnitTestResources.php
tests/qunit/data/load.mock.php
tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js [deleted file]
tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
tests/selenium/wdio.conf.js

index 09f86a3..f230c60 100644 (file)
@@ -1,3 +1,4 @@
 *.sh eol=lf
 *.icc binary
 *.webp binary
+*.mp3 binary
\ No newline at end of file
index 3e97aab..b991e11 100644 (file)
@@ -42,6 +42,7 @@ sftp-config.json
 /StartProfiler.php
 
 # Building & testing
+npm-debug.log
 node_modules/
 /tests/phpunit/phpunit.phar
 
@@ -49,6 +50,7 @@ node_modules/
 /vendor
 /composer.lock
 /composer.local.json
+/composer.phar
 
 # MediaWiki UI documentation
 /docs/kss/static
index 2134fc5..5a76fb9 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -63,6 +63,7 @@ aude <aude.wiki@gmail.com>
 Audrey Tang <audreyt@audreyt.org>
 Audrey Tang <audreyt@audreyt.org> <au@localhost>
 ayush_garg <ayush.ce13@iitp.ac.in>
+Bae Junehyeon <devunt@gmail.com>
 Bahodir Mansurov <bmansurov@wikimedia.org>
 Bartosz Dziewoński <matma.rex@gmail.com>
 Bartosz Dziewoński <matma.rex@gmail.com> <bdziewonski@wikimedia.org>
@@ -227,7 +228,6 @@ Jon Robson <jrobson@wikimedia.org>
 Jon Robson <jrobson@wikimedia.org> <jdlrobson@gmail.com>
 Juliusz Gonera <jgonera@gmail.com>
 Juliusz Gonera <jgonera@gmail.com> <jgonera@wikimedia.org>
-JuneHyeon Bae <devunt@gmail.com>
 Jure Kajzer <freak@drajv.si>
 Jure Kajzer <freak@drajv.si> <freakolowsky@users.mediawiki.org>
 Justin Du <justin.d128@gmail.com>
diff --git a/.stylelintrc b/.stylelintrc
deleted file mode 100644 (file)
index 27e289d..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-       "extends": "stylelint-config-wikimedia",
-       "rules": {
-               "no-descending-specificity": null,
-
-               "selector-no-id": null
-       }
-}
diff --git a/.stylelintrc.json b/.stylelintrc.json
new file mode 100644 (file)
index 0000000..27e289d
--- /dev/null
@@ -0,0 +1,8 @@
+{
+       "extends": "stylelint-config-wikimedia",
+       "rules": {
+               "no-descending-specificity": null,
+
+               "selector-no-id": null
+       }
+}
diff --git a/CREDITS b/CREDITS
index 14c454e..c38c3fc 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -78,6 +78,7 @@ The following list can be found parsed under Special:Version/Credits -->
 * awu42
 * ayush_garg
 * Azliq7
+* Bae Junehyeon
 * Bagariavivek
 * Bahodir Mansurov
 * balloonguy
@@ -312,7 +313,6 @@ The following list can be found parsed under Special:Version/Credits -->
 * Julian Ostrow
 * Juliano F. Ravasi
 * Juliusz Gonera
-* JuneHyeon Bae
 * Jure Kajzer
 * Justin Du
 * Kai Nissen
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 452cb35..c4c56e8 100644 (file)
@@ -33,6 +33,12 @@ 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
@@ -49,11 +55,17 @@ section).
 * Added RecentChangesPurgeRows hook to allow extensions to purge data that
   depends on the recentchanges table.
 * Added JS config values wgDiffOldId/wgDiffNewId to the output of diff pages.
-
-=== Languages updated in 1.30 ===
-
-* Support for kbp (Kabɩyɛ / Kabiyè) was added.
-* Support for skr (Saraiki, سرائیکی) was added.
+* (T2424) Added direct unwatch links to entries in Special:Watchlist (if the
+  'watchlistunwatchlinks' preference option is enabled). With JavaScript
+  enabled, these links toggle so the user can also re-watch pages that have
+  just been unwatched.
+* Added $wgParserTestMediaHandlers, where mock media handlers can be passed to
+  MediaHandlerFactory for parser tests.
+* 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 ===
 
@@ -99,7 +111,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),
@@ -143,6 +157,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()
@@ -158,6 +173,30 @@ changes to languages because of Phabricator reports.
   nothing and is deprecated.
 * mw.util.escapeId() was deprecated, use escapeIdForAttribute() or
   escapeIdForLink().
+* MagicWord::replaceMultiple() (deprecated in 1.25) was removed.
+* WikiImporter now requires the second parameter to be an instance of the Config,
+  class. Prior to that, the Config parameter was optional (a behavior deprecated in
+  1.25).
+* 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.
+* 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.
 
 == Compatibility ==
 MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for
index d44a305..3a2ae10 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',
@@ -291,7 +291,7 @@ $wgAutoloadLocalClasses = [
        'Config' => __DIR__ . '/includes/config/Config.php',
        'ConfigException' => __DIR__ . '/includes/config/ConfigException.php',
        'ConfigFactory' => __DIR__ . '/includes/config/ConfigFactory.php',
-       'ConfiguredReadOnlyMode' => __DIR__ . '/includes/ReadOnlyMode.php',
+       'ConfiguredReadOnlyMode' => __DIR__ . '/includes/ConfiguredReadOnlyMode.php',
        'ConstantDependency' => __DIR__ . '/includes/cache/CacheDependency.php',
        'Content' => __DIR__ . '/includes/content/Content.php',
        'ContentHandler' => __DIR__ . '/includes/content/ContentHandler.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',
@@ -617,7 +617,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 +802,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 +966,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 +984,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',
@@ -1180,7 +1178,7 @@ $wgAutoloadLocalClasses = [
        'RangeChronologicalPager' => __DIR__ . '/includes/pager/RangeChronologicalPager.php',
        'RangeDifference' => __DIR__ . '/includes/diff/DiffEngine.php',
        'RawAction' => __DIR__ . '/includes/actions/RawAction.php',
-       'RawMessage' => __DIR__ . '/includes/Message.php',
+       'RawMessage' => __DIR__ . '/includes/RawMessage.php',
        'ReadOnlyError' => __DIR__ . '/includes/exception/ReadOnlyError.php',
        'ReadOnlyMode' => __DIR__ . '/includes/ReadOnlyMode.php',
        'ReassignEdits' => __DIR__ . '/maintenance/reassignEdits.php',
@@ -1282,7 +1280,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',
index bc48360..a7983a0 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.22.5",
                "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",
@@ -53,7 +54,7 @@
                "jakub-onderka/php-parallel-lint": "0.9.2",
                "jetbrains/phpstorm-stubs": "dev-master#1b9906084d6635456fcf3f3a01f0d7d5b99a578a",
                "justinrainbow/json-schema": "~5.2",
-               "mediawiki/mediawiki-codesniffer": "0.10.1",
+               "mediawiki/mediawiki-codesniffer": "0.11.0",
                "monolog/monolog": "~1.22.1",
                "nikic/php-parser": "2.1.0",
                "nmred/kafka-php": "0.1.5",
index 44ec764..dbc9204 100644 (file)
@@ -17,7 +17,7 @@ description of the tables and their contents, please see:
 
 To make a read query, something like this usually suffices:
 
-$dbr = wfGetDB( DB_SLAVE );
+$dbr = wfGetDB( DB_REPLICA );
 $res = $dbr->select( /* ...see docs... */ );
 foreach ( $res as $row ) {
        ...
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 b85e1d6..b73ecbd 100644 (file)
@@ -73,8 +73,8 @@ class AuthPlugin {
        /**
         * Modify options in the login template.
         *
-        * @param BaseTemplate $template
-        * @param string $type 'signup' or 'login'. Added in 1.16.
+        * @param BaseTemplate &$template
+        * @param string &$type 'signup' or 'login'. Added in 1.16.
         */
        public function modifyUITemplate( &$template, &$type ) {
                # Override this!
@@ -124,7 +124,7 @@ class AuthPlugin {
         *
         * @deprecated since 1.26, use the UserLoggedIn hook instead. And assigning
         *  a different User object to $user is no longer supported.
-        * @param User $user
+        * @param User &$user
         * @return bool
         */
        public function updateUser( &$user ) {
@@ -286,7 +286,7 @@ class AuthPlugin {
         *
         * @deprecated since 1.26, use the UserLoggedIn hook instead. And assigning
         *  a different User object to $user is no longer supported.
-        * @param User $user
+        * @param User &$user
         * @param bool $autocreate True if user is being autocreated on login
         */
        public function initUser( &$user, $autocreate = false ) {
@@ -306,7 +306,7 @@ class AuthPlugin {
        /**
         * Get an instance of a User object
         *
-        * @param User $user
+        * @param User &$user
         *
         * @return AuthPluginUser
         */
@@ -359,6 +359,7 @@ class AuthPluginUser {
 
        /**
         * @deprecated since 1.28, use SessionManager::invalidateSessionForUser() instead.
+        * @return bool
         */
        public function resetAuthToken() {
                # Override this!
index 2a04879..0b17e93 100644 (file)
@@ -44,40 +44,40 @@ class Block {
        public $mParentBlockId;
 
        /** @var int */
-       protected $mId;
+       private $mId;
 
        /** @var bool */
-       protected $mFromMaster;
+       private $mFromMaster;
 
        /** @var bool */
-       protected $mBlockEmail;
+       private $mBlockEmail;
 
        /** @var bool */
-       protected $mDisableUsertalk;
+       private $mDisableUsertalk;
 
        /** @var bool */
-       protected $mCreateAccount;
+       private $mCreateAccount;
 
        /** @var User|string */
-       protected $target;
+       private $target;
 
        /** @var int Hack for foreign blocking (CentralAuth) */
-       protected $forcedTargetID;
+       private $forcedTargetID;
 
        /** @var int Block::TYPE_ constant. Can only be USER, IP or RANGE internally */
-       protected $type;
+       private $type;
 
        /** @var User */
-       protected $blocker;
+       private $blocker;
 
        /** @var bool */
-       protected $isHardblock;
+       private $isHardblock;
 
        /** @var bool */
-       protected $isAutoblocking;
+       private $isAutoblocking;
 
        /** @var string|null */
-       protected $systemBlockType;
+       private $systemBlockType;
 
        # TYPE constants
        const TYPE_USER = 1;
@@ -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,7 +492,7 @@ class Block {
                        self::purgeExpired();
                }
 
-               $row = $this->getDatabaseArray();
+               $row = $this->getDatabaseArray( $dbw );
                $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" );
 
                $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
@@ -558,7 +562,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 +587,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 +604,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 +616,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 );
        }
 
        /**
@@ -958,6 +958,7 @@ class Block {
 
        /**
         * Get the system block type, if any
+        * @since 1.29
         * @return string|null
         */
        public function getSystemBlockType() {
@@ -1353,7 +1354,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 ];
                }
@@ -1450,6 +1451,8 @@ class Block {
         * Set the 'BlockID' cookie to this block's ID and expiry time. The cookie's expiry will be
         * the same as the block's, to a maximum of 24 hours.
         *
+        * @since 1.29
+        *
         * @param WebResponse $response The response on which to set the cookie.
         */
        public function setCookie( WebResponse $response ) {
@@ -1472,6 +1475,8 @@ class Block {
        /**
         * Unset the 'BlockID' cookie.
         *
+        * @since 1.29
+        *
         * @param WebResponse $response The response on which to unset the cookie.
         */
        public static function clearCookie( WebResponse $response ) {
@@ -1482,6 +1487,9 @@ class Block {
         * Get the BlockID cookie's value for this block. This is usually the block ID concatenated
         * with an HMAC in order to avoid spoofing (T152951), but if wgSecretKey is not set will just
         * be the block ID.
+        *
+        * @since 1.29
+        *
         * @return string The block ID, probably concatenated with "!" and the HMAC.
         */
        public function getCookieValue() {
@@ -1493,15 +1501,19 @@ class Block {
                        return $id;
                }
                $hmac = MWCryptHash::hmac( $id, $secretKey, false );
-               $cookieValue =  $id . '!' . $hmac;
+               $cookieValue = $id . '!' . $hmac;
                return $cookieValue;
        }
 
        /**
         * Get the stored ID from the 'BlockID' cookie. The cookie's value is usually a combination of
         * the ID and a HMAC (see Block::setCookie), but will sometimes only be the ID.
+        *
+        * @since 1.29
+        *
         * @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..0c86c1e
--- /dev/null
@@ -0,0 +1,567 @@
+<?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 {
+
+       /**
+        * 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;
+
+       /**
+        * @param string $key A key such as "rev_comment" identifying the comment
+        *  field being fetched.
+        */
+       public function __construct( $key ) {
+               global $wgCommentTableSchemaMigrationStage;
+
+               $this->key = $key;
+               $this->stage = $wgCommentTableSchemaMigrationStage;
+       }
+
+       /**
+        * 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 );
+                       }
+               }
+
+               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 );
+                       }
+
+                       $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 ) {
+                               $comment->id = $dbw->nextSequenceValue( 'comment_comment_id_seq' );
+                               $dbw->insert(
+                                       'comment',
+                                       [
+                                               'comment_id' => $comment->id,
+                                               '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] = $comment->text;
+               }
+
+               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 ];
+       }
+
+       /**
+        * Prepare for the insertion of a row with a comment
+        *
+        * @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;
+       }
+
+       /**
+        * Prepare for the insertion of a row with a comment and temporary table
+        *
+        * 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;
+       }
+}
diff --git a/includes/ConfiguredReadOnlyMode.php b/includes/ConfiguredReadOnlyMode.php
new file mode 100644 (file)
index 0000000..af7c7cb
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * A read-only mode service which does not depend on LoadBalancer.
+ * To obtain an instance, use MediaWikiServices::getConfiguredReadOnlyMode().
+ *
+ * @since 1.29
+ */
+class ConfiguredReadOnlyMode {
+       /** @var Config */
+       private $config;
+
+       /** @var string|bool|null */
+       private $fileReason;
+
+       /** @var string|null */
+       private $overrideReason;
+
+       public function __construct( Config $config ) {
+               $this->config = $config;
+       }
+
+       /**
+        * Check whether the wiki is in read-only mode.
+        *
+        * @return bool
+        */
+       public function isReadOnly() {
+               return $this->getReason() !== false;
+       }
+
+       /**
+        * Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
+        *
+        * @return string|bool String when in read-only mode; false otherwise
+        */
+       public function getReason() {
+               if ( $this->overrideReason !== null ) {
+                       return $this->overrideReason;
+               }
+               $confReason = $this->config->get( 'ReadOnly' );
+               if ( $confReason !== null ) {
+                       return $confReason;
+               }
+               if ( $this->fileReason === null ) {
+                       // Cache for faster access next time
+                       $readOnlyFile = $this->config->get( 'ReadOnlyFile' );
+                       if ( is_file( $readOnlyFile ) && filesize( $readOnlyFile ) > 0 ) {
+                               $this->fileReason = file_get_contents( $readOnlyFile );
+                       } else {
+                               $this->fileReason = false;
+                       }
+               }
+               return $this->fileReason;
+       }
+
+       /**
+        * Set the read-only mode, which will apply for the remainder of the
+        * request or until a service reset.
+        *
+        * @param string|null $msg
+        */
+       public function setReason( $msg ) {
+               $this->overrideReason = $msg;
+       }
+
+       /**
+        * Clear the cache of the read only file
+        */
+       public function clearCache() {
+               $this->fileReason = null;
+       }
+}
index 8e38121..cf3e569 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;
@@ -959,6 +961,23 @@ $wgTrustedMediaFormats = [
  */
 $wgMediaHandlers = [];
 
+/**
+ * Media handler overrides for parser tests (they don't need to generate actual
+ * thumbnails, so a mock will do)
+ */
+$wgParserTestMediaHandlers = [
+       'image/jpeg' => 'MockBitmapHandler',
+       'image/png' => 'MockBitmapHandler',
+       'image/gif' => 'MockBitmapHandler',
+       'image/tiff' => 'MockBitmapHandler',
+       'image/webp' => 'MockBitmapHandler',
+       'image/x-ms-bmp' => 'MockBitmapHandler',
+       'image/x-bmp' => 'MockBitmapHandler',
+       'image/x-xcf' => 'MockBitmapHandler',
+       'image/svg+xml' => 'MockSvgHandler',
+       'image/vnd.djvu' => 'MockDjVuHandler',
+];
+
 /**
  * Plugins for page content model handling.
  * Each entry in the array maps a model id to a class name or callback
@@ -2036,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 = [];
@@ -3218,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
@@ -4133,6 +4144,7 @@ $wgContentNamespaces = [ NS_MAIN ];
  * Optional array of namespaces which should be blacklisted from Special:ShortPages
  * Only pages inside $wgContentNamespaces but not $wgShortPagesNamespaceBlacklist will
  * be shown on that page.
+ * @since 1.30
  */
 $wgShortPagesNamespaceBlacklist = [];
 
@@ -4883,7 +4895,7 @@ $wgDefaultUserOptions = [
        'date' => 'default',
        'diffonly' => 0,
        'disablemail' => 0,
-       'editfont' => 'default',
+       'editfont' => 'monospace',
        'editondblclick' => 0,
        'editsectiononrightclick' => 0,
        'enotifminoredits' => 0,
@@ -4932,6 +4944,7 @@ $wgDefaultUserOptions = [
        'watchlisthidepatrolled' => 0,
        'watchlisthidecategorization' => 1,
        'watchlistreloadautomatically' => 0,
+       'watchlistunwatchlinks' => 0,
        'watchmoves' => 0,
        'watchrollback' => 0,
        'wllimit' => 250,
@@ -5796,7 +5809,7 @@ $wgPasswordAttemptThrottle = [
        // Long term limit. We need to balance the risk
        // of somebody using this as a DoS attack to lock someone
        // out of their account, and someone doing a brute force attack.
-       [ 'count' => 150, 'seconds' => 60*60*48 ],
+       [ 'count' => 150, 'seconds' => 60 * 60 * 48 ],
 ];
 
 /**
@@ -6827,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
  */
@@ -8255,6 +8273,7 @@ $wgShellLocale = 'C.UTF-8';
 
 /**
  * Timeout for HTTP requests done internally, in seconds.
+ * @var int
  */
 $wgHTTPTimeout = 25;
 
@@ -8278,10 +8297,13 @@ $wgHTTPProxy = false;
  * Local virtual hosts.
  *
  * This lists domains that are configured as virtual hosts on the same machine.
- * If a request is to be made to a domain listed here, or any subdomain thereof,
- * then no proxy will be used.
- * Command-line scripts are not affected by this setting and will always use
- * proxy if it is configured.
+ *
+ * This affects the following:
+ * - MWHttpRequest: If a request is to be made to a domain listed here, or any
+ *   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.
+ *
  * @since 1.25
  */
 $wgLocalVirtualHosts = [];
@@ -8741,6 +8763,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 9d83fbd..4451867 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;
        }
 
        /**
@@ -852,15 +846,12 @@ class EditPage {
 
        /**
         * This function collects the form data and uses it to populate various member variables.
-        * @param WebRequest $request
+        * @param WebRequest &$request
         * @throws ErrorPageError
         */
        public function importFormData( &$request ) {
                global $wgContLang, $wgUser;
 
-               # Allow users to change the mode for testing
-               $this->oouiEnabled = $request->getFuzzyBool( 'ooui', $this->oouiEnabled );
-
                # Section edit can come from either the form or a link
                $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
 
@@ -1080,7 +1071,7 @@ class EditPage {
         * this method should be overridden and return the page text that will be used
         * for saving, preview parsing and so on...
         *
-        * @param WebRequest $request
+        * @param WebRequest &$request
         * @return string|null
         */
        protected function importContentFormData( &$request ) {
@@ -1432,7 +1423,7 @@ class EditPage {
        /**
         * Make sure the form isn't faking a user's credentials.
         *
-        * @param WebRequest $request
+        * @param WebRequest &$request
         * @return bool
         * @private
         */
@@ -1475,7 +1466,7 @@ class EditPage {
 
        /**
         * Attempt submission
-        * @param array|bool $resultDetails See docs for $result in internalAttemptSave
+        * @param array|bool &$resultDetails See docs for $result in internalAttemptSave
         * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
         * @return Status The resulting status object.
         */
@@ -1721,12 +1712,12 @@ class EditPage {
        /**
         * Attempt submission (no UI)
         *
-        * @param array $result Array to add statuses to, currently with the
+        * @param array &$result Array to add statuses to, currently with the
         *   possible keys:
         *   - 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.
@@ -2667,7 +2658,7 @@ class EditPage {
                $wgOut->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',
@@ -2710,7 +2701,7 @@ class EditPage {
 
                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?
@@ -2767,13 +2758,7 @@ class EditPage {
                $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
                $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
 
-               // Preserve &ooui=1 / &ooui=0 from URL parameters after submitting the page for preview
-               $wgOut->addHTML( Html::hidden( 'ooui', $this->oouiEnabled ? '1' : '0' ) );
-
-               // following functions will need OOUI, enable it only once; here.
-               if ( $this->oouiEnabled ) {
-                       $wgOut->enableOOUI();
-               }
+               $wgOut->enableOOUI();
 
                if ( $this->section == 'new' ) {
                        $this->showSummaryInput( true, $this->summary );
@@ -3080,47 +3065,6 @@ class EditPage {
        }
 
        /**
-        * Standard summary input and label (wgSummary), abstracted so EditPage
-        * subclasses may reorganize the form.
-        * Note that you do not need to worry about the label's for=, it will be
-        * inferred by the id given to the input. You can remove them both by
-        * passing [ 'id' => false ] to $userInputAttrs.
-        *
-        * @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
-       ) {
-               $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
-               $inputAttrs += Linker::tooltipAndAccesskeyAttribs( 'summary' );
-
-               $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
-                       'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
-                       'id' => "wpSummaryLabel"
-               ];
-
-               $label = null;
-               if ( $labelText ) {
-                       $label = Xml::tags(
-                               'label',
-                               $inputAttrs['id'] ? [ 'for' => $inputAttrs['id'] ] : null,
-                               $labelText
-                       );
-                       $label = Xml::tags( 'span', $spanLabelAttrs, $label );
-               }
-
-               $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
-
-               return [ $label, $input ];
-       }
-
-       /**
-        * Same as self::getSummaryInput, but uses OOUI, instead of plain HTML.
         * Builds a standard summary input with a label.
         *
         * @param string $summary The value of the summary input
@@ -3178,20 +3122,11 @@ class EditPage {
                }
 
                $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
-               if ( $this->oouiEnabled ) {
-                       $wgOut->addHTML( $this->getSummaryInputOOUI(
+               $wgOut->addHTML( $this->getSummaryInputOOUI(
                                $summary,
                                $labelText,
                                [ 'class' => $summaryClass ]
                        ) );
-               } else {
-                       list( $label, $input ) = $this->getSummaryInput(
-                               $summary,
-                               $labelText,
-                               [ 'class' => $summaryClass ]
-                       );
-                       $wgOut->addHTML( "{$label} {$input}" );
-               }
        }
 
        /**
@@ -3225,16 +3160,13 @@ class EditPage {
 
        protected function showFormBeforeText() {
                global $wgOut;
-               $section = htmlspecialchars( $this->section );
-               $wgOut->addHTML( <<<HTML
-<input type='hidden' value="{$section}" name="wpSection"/>
-<input type='hidden' value="{$this->starttime}" name="wpStarttime" />
-<input type='hidden' value="{$this->edittime}" name="wpEdittime" />
-<input type='hidden' value="{$this->editRevId}" name="editRevId" />
-<input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
-
-HTML
-               );
+
+               $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 ) );
+
                if ( !$this->checkUnicodeCompliantBrowser() ) {
                        $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
                }
@@ -3498,8 +3430,8 @@ HTML
        }
 
        /**
-        * Inserts optional text shown below edit and upload forms. Can be used to offer special characters not present on
-        * most keyboards for copying/pasting.
+        * Inserts optional text shown below edit and upload forms. Can be used to offer special
+        * characters not present on most keyboards for copying/pasting.
         */
        protected function showEditTools() {
                global $wgOut;
@@ -3605,19 +3537,11 @@ HTML
                        $wgOut->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->getCheckboxesOOUI(
+                       $tabindex,
+                       [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
+               );
+               $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
 
                $wgOut->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
 
@@ -3707,23 +3631,15 @@ HTML
                } 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',
+               ] );
        }
 
        /**
@@ -3771,8 +3687,9 @@ HTML
         */
        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',
@@ -3780,11 +3697,10 @@ HTML
                                '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',
@@ -3792,7 +3708,10 @@ HTML
                                '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 ) ) {
@@ -3801,7 +3720,8 @@ HTML
                        }
 
                        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;
                        }
                }
 
@@ -4166,7 +4086,7 @@ HTML
         *   where bool indicates the checked status of the checkbox
         * @return array
         */
-       protected function getCheckboxesDefinition( $checked ) {
+       public function getCheckboxesDefinition( $checked ) {
                global $wgUser;
                $checkboxes = [];
 
@@ -4205,7 +4125,7 @@ HTML
         * Returns an array of html code of the following checkboxes old style:
         * minor and watch
         *
-        * @param int $tabindex Current tabindex
+        * @param int &$tabindex Current tabindex
         * @param array $checked See getCheckboxesDefinition()
         * @return array
         */
@@ -4263,7 +4183,7 @@ HTML
         * Returns an array of html code of the following checkboxes:
         * minor and watch
         *
-        * @param int $tabindex Current tabindex
+        * @param int &$tabindex Current tabindex
         * @param array $checked Array of checkbox => bool, where bool indicates the checked
         *                 status of the checkbox
         *
@@ -4336,7 +4256,7 @@ HTML
                $newPage = !$this->mTitle->exists();
 
                if ( $labelAsPublish ) {
-                       $buttonLabelKey =  $newPage ? 'publishpage' : 'publishchanges';
+                       $buttonLabelKey = $newPage ? 'publishpage' : 'publishchanges';
                } else {
                        $buttonLabelKey = $newPage ? 'savearticle' : 'savechanges';
                }
@@ -4348,7 +4268,7 @@ HTML
         * Returns an array of html code of the following buttons:
         * save, diff and preview
         *
-        * @param int $tabindex Current tabindex
+        * @param int &$tabindex Current tabindex
         *
         * @return array
         */
@@ -4361,74 +4281,56 @@ HTML
                        '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;
index 3b2283b..2f7f75b 100644 (file)
@@ -126,7 +126,7 @@ class FauxRequest extends WebRequest {
 
        /**
         * @since 1.26
-        * @param string $name Unprefixed name of the cookie to set
+        * @param string $key Unprefixed name of the cookie to set
         * @param string|null $value Value of the cookie to set
         * @param string|null $prefix Cookie prefix. Defaults to $wgCookiePrefix
         */
@@ -152,6 +152,7 @@ class FauxRequest extends WebRequest {
 
        /**
         * @since 1.25
+        * @param string $url
         */
        public function setRequestURL( $url ) {
                $this->requestUrl = $url;
@@ -160,6 +161,7 @@ class FauxRequest extends WebRequest {
        /**
         * @since 1.25 MWException( "getRequestURL not implemented" )
         * no longer thrown.
+        * @return string
         */
        public function getRequestURL() {
                if ( $this->requestUrl === null ) {
index 96a88d3..0def6a0 100644 (file)
@@ -72,7 +72,8 @@ class FeedUtils {
        /**
         * Format a diff for the newsfeed
         *
-        * @param object $row Row from the recentchanges table
+        * @param object $row Row from the recentchanges table, including fields as
+        *  appropriate for CommentStore
         * @return string
         */
        public static function formatDiff( $row ) {
@@ -88,7 +89,9 @@ class FeedUtils {
                        $timestamp,
                        $row->rc_deleted & Revision::DELETED_COMMENT
                                ? wfMessage( 'rev-deleted-comment' )->escaped()
-                               : $row->rc_comment,
+                               : CommentStore::newKey( 'rc_comment' )
+                                       // Legacy from RecentChange::selectFields() via ChangesListSpecialPage::doMainQuery()
+                                       ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row )->text,
                        $actiontext
                );
        }
index 8a1cd35..8c843c4 100644 (file)
@@ -143,9 +143,9 @@ class FileDeleteForm {
        /**
         * Really delete the file
         *
-        * @param Title $title
-        * @param File $file
-        * @param string $oldimage Archive name
+        * @param Title &$title
+        * @param File &$file
+        * @param string &$oldimage Archive name
         * @param string $reason Reason of the deletion
         * @param bool $suppress Whether to mark all deleted versions as restricted
         * @param User $user User object performing the request
@@ -398,8 +398,8 @@ class FileDeleteForm {
         * value was provided, does it correspond to an
         * existing, local, old version of this file?
         *
-        * @param File $file
-        * @param File $oldfile
+        * @param File &$file
+        * @param File &$oldfile
         * @param File $oldimage
         * @return bool
         */
index 70784ba..49159ed 100644 (file)
@@ -241,7 +241,7 @@ function wfArrayFilterByKey( array $arr, callable $callback ) {
  * @param string|int $key
  * @param mixed $value
  * @param mixed $default
- * @param array $changed Array to alter
+ * @param array &$changed Array to alter
  * @throws MWException
  */
 function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
@@ -1750,7 +1750,7 @@ function wfEscapeWikiText( $text ) {
  * If source is NULL, it just returns the value, it doesn't set the variable
  * If force is true, it will set the value even if source is NULL
  *
- * @param mixed $dest
+ * @param mixed &$dest
  * @param mixed $source
  * @param bool $force
  * @return mixed
@@ -1766,7 +1766,7 @@ function wfSetVar( &$dest, $source, $force = false ) {
 /**
  * As for wfSetVar except setting a bit
  *
- * @param int $dest
+ * @param int &$dest
  * @param int $bit
  * @param bool $state
  *
@@ -2234,7 +2234,8 @@ function wfIniGetBool( $setting ) {
  * (https://bugs.php.net/bug.php?id=26285) and the locale problems on Linux in
  * PHP 5.2.6+ (bug backported to earlier distro releases of PHP).
  *
- * @param string ... strings to escape and glue together, or a single array of strings parameter
+ * @param string $args,... strings to escape and glue together,
+ *  or a single array of strings parameter
  * @return string
  */
 function wfEscapeShellArg( /*...*/ ) {
@@ -2618,7 +2619,7 @@ function wfShellWikiCmd( $script, array $parameters = [], array $options = [] )
  * @param string $old
  * @param string $mine
  * @param string $yours
- * @param string $result
+ * @param string &$result
  * @return bool
  */
 function wfMerge( $old, $mine, $yours, &$result ) {
index da1a8da..6467777 100644 (file)
@@ -92,7 +92,7 @@ class Licenses extends HTMLFormField {
        }
 
        /**
-        * @param array $list
+        * @param array &$list
         * @param array $path
         * @param mixed $item
         */
index 33e30c9..aedb704 100644 (file)
@@ -154,6 +154,11 @@ class Linker {
         * @since 1.16.3
         * @deprecated since 1.28, use MediaWiki\Linker\LinkRenderer instead
         * @see Linker::link
+        * @param Title $target
+        * @param string $html
+        * @param array $customAttribs
+        * @param array $query
+        * @param string|array $options
         * @return string
         */
        public static function linkKnown(
@@ -1350,7 +1355,7 @@ class Linker {
        /**
         * @param Title $contextTitle
         * @param string $target
-        * @param string $text
+        * @param string &$text
         * @return string
         */
        public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
index 1703179..6e7799a 100644 (file)
@@ -518,7 +518,7 @@ class MagicWord {
         * Returns true if the text matches the word, and alters the
         * input string, removing all instances of the word
         *
-        * @param string $text
+        * @param string &$text
         *
         * @return bool
         */
@@ -534,7 +534,7 @@ class MagicWord {
        }
 
        /**
-        * @param string $text
+        * @param string &$text
         * @return bool
         */
        public function matchStartAndRemove( &$text ) {
@@ -646,38 +646,11 @@ class MagicWord {
                return $this->mModified;
        }
 
-       /**
-        * $magicarr is an associative array of (magic word ID => replacement)
-        * This method uses the php feature to do several replacements at the same time,
-        * thereby gaining some efficiency. The result is placed in the out variable
-        * $result. The return value is true if something was replaced.
-        * @deprecated since 1.25, unused
-        *
-        * @param array $magicarr
-        * @param string $subject
-        * @param string $result
-        *
-        * @return bool
-        */
-       public function replaceMultiple( $magicarr, $subject, &$result ) {
-               wfDeprecated( __METHOD__, '1.25' );
-               $search = [];
-               $replace = [];
-               foreach ( $magicarr as $id => $replacement ) {
-                       $mw = self::get( $id );
-                       $search[] = $mw->getRegex();
-                       $replace[] = $replacement;
-               }
-
-               $result = preg_replace( $search, $replace, $subject );
-               return $result !== $subject;
-       }
-
        /**
         * Adds all the synonyms of this MagicWord to an array, to allow quick
         * lookup in a list of magic words
         *
-        * @param array $array
+        * @param array &$array
         * @param string $value
         */
        public function addToArray( &$array, $value ) {
index 6a9ead5..5856e21 100644 (file)
@@ -95,13 +95,22 @@ class MagicWordArray {
        public function getBaseRegex() {
                if ( is_null( $this->baseRegex ) ) {
                        $this->baseRegex = [ 0 => '', 1 => '' ];
+                       $allGroups = [];
                        foreach ( $this->names as $name ) {
                                $magic = MagicWord::get( $name );
                                $case = intval( $magic->isCaseSensitive() );
                                foreach ( $magic->getSynonyms() as $i => $syn ) {
                                        // Group name must start with a non-digit in PCRE 8.34+
                                        $it = strtr( $i, '0123456789', 'abcdefghij' );
-                                       $group = "(?P<{$it}_{$name}>" . preg_quote( $syn, '/' ) . ')';
+                                       $groupName = $it . '_' . $name;
+                                       $group = '(?P<' . $groupName . '>' . preg_quote( $syn, '/' ) . ')';
+                                       // look for same group names to avoid same named subpatterns in the regex
+                                       if ( isset( $allGroups[$groupName] ) ) {
+                                               throw new MWException(
+                                                       __METHOD__ . ': duplicate internal name in magic word array: ' . $name
+                                               );
+                                       }
+                                       $allGroups[$groupName] = true;
                                        if ( $this->baseRegex[$case] === '' ) {
                                                $this->baseRegex[$case] = $group;
                                        } else {
@@ -260,7 +269,7 @@ class MagicWordArray {
         * Returns an associative array, ID => param value, for all items that match
         * Removes the matched items from the input string (passed by reference)
         *
-        * @param string $text
+        * @param string &$text
         *
         * @return array
         */
@@ -304,7 +313,7 @@ class MagicWordArray {
         * Return false if no match found and $text is not modified.
         * Does not match parameters.
         *
-        * @param string $text
+        * @param string &$text
         *
         * @return int|bool False on failure
         */
index 4e47184..7b59ee9 100644 (file)
@@ -607,7 +607,7 @@ class MediaWiki {
                        $request->wasPosted() &&
                        $output->getRedirect() &&
                        $lbFactory->hasOrMadeRecentMasterChanges( INF )
-               ) ? self::getUrlDomainDistance( $output->getRedirect(), $context ) : false;
+               ) ? self::getUrlDomainDistance( $output->getRedirect() ) : false;
 
                $allowHeaders = !( $output->isDisabled() || headers_sent() );
                if ( $urlDomainDistance === 'local' || $urlDomainDistance === 'remote' ) {
@@ -676,34 +676,14 @@ class MediaWiki {
 
        /**
         * @param string $url
-        * @param IContextSource $context
         * @return string Either "local", "remote" if in the farm, "external" otherwise
         */
-       private static function getUrlDomainDistance( $url, IContextSource $context ) {
-               static $relevantKeys = [ 'host' => true, 'port' => true ];
-
-               $infoCandidate = wfParseUrl( $url );
-               if ( $infoCandidate === false ) {
-                       return 'external';
-               }
-
-               $infoCandidate = array_intersect_key( $infoCandidate, $relevantKeys );
-               $clusterHosts = array_merge(
-                       // Local wiki host (the most common case)
-                       [ $context->getConfig()->get( 'CanonicalServer' ) ],
-                       // Any local/remote wiki virtual hosts for this wiki farm
-                       $context->getConfig()->get( 'LocalVirtualHosts' )
-               );
-
-               foreach ( $clusterHosts as $i => $clusterHost ) {
-                       $parseUrl = wfParseUrl( $clusterHost );
-                       if ( !$parseUrl ) {
-                               continue;
-                       }
-                       $infoHost = array_intersect_key( $parseUrl, $relevantKeys );
-                       if ( $infoCandidate === $infoHost ) {
-                               return ( $i === 0 ) ? 'local' : 'remote';
-                       }
+       private static function getUrlDomainDistance( $url ) {
+               $clusterWiki = WikiMap::getWikiFromUrl( $url );
+               if ( $clusterWiki === wfWikiID() ) {
+                       return 'local'; // the current wiki
+               } elseif ( $clusterWiki !== false ) {
+                       return 'remote'; // another wiki in this cluster/farm
                }
 
                return 'external';
@@ -970,7 +950,7 @@ class MediaWiki {
        }
 
        /**
-        * @param integer $n Number of jobs to try to run
+        * @param int $n Number of jobs to try to run
         * @param LoggerInterface $runJobsLogger
         */
        private function triggerSyncJobs( $n, LoggerInterface $runJobsLogger ) {
@@ -979,7 +959,7 @@ class MediaWiki {
        }
 
        /**
-        * @param integer $n Number of jobs to try to run
+        * @param int $n Number of jobs to try to run
         * @param LoggerInterface $runJobsLogger
         * @return bool Success
         */
index 48ff97b..9d63869 100644 (file)
@@ -56,7 +56,7 @@ class MergeHistory {
        /** @var MWTimestamp|bool Timestamp upto which history from the source will be merged */
        protected $timestampLimit;
 
-       /** @var integer Number of revisions merged (for Special:MergeHistory success message) */
+       /** @var int Number of revisions merged (for Special:MergeHistory success message) */
        protected $revisionsMerged;
 
        /**
index be6b0af..0240fa7 100644 (file)
@@ -488,7 +488,7 @@ class Message implements MessageSpecifier, Serializable {
         *
         * @since 1.17
         *
-        * @param mixed ... Parameters as strings or arrays from
+        * @param mixed $args,... Parameters as strings or arrays from
         *  Message::numParam() and the like, or a single array of parameters.
         *
         * @return Message $this
@@ -1344,56 +1344,3 @@ class Message implements MessageSpecifier, Serializable {
                return $this->extractParam( new RawMessage( $vars, $params ), $format );
        }
 }
-
-/**
- * Variant of the Message class.
- *
- * Rather than treating the message key as a lookup
- * value (which is passed to the MessageCache and
- * translated as necessary), a RawMessage key is
- * treated as the actual message.
- *
- * All other functionality (parsing, escaping, etc.)
- * is preserved.
- *
- * @since 1.21
- */
-class RawMessage extends Message {
-
-       /**
-        * Call the parent constructor, then store the key as
-        * the message.
-        *
-        * @see Message::__construct
-        *
-        * @param string $text Message to use.
-        * @param array $params Parameters for the message.
-        *
-        * @throws InvalidArgumentException
-        */
-       public function __construct( $text, $params = [] ) {
-               if ( !is_string( $text ) ) {
-                       throw new InvalidArgumentException( '$text must be a string' );
-               }
-
-               parent::__construct( $text, $params );
-
-               // The key is the message.
-               $this->message = $text;
-       }
-
-       /**
-        * Fetch the message (in this case, the key).
-        *
-        * @return string
-        */
-       public function fetchMessage() {
-               // Just in case the message is unset somewhere.
-               if ( $this->message === null ) {
-                       $this->message = $this->key;
-               }
-
-               return $this->message;
-       }
-
-}
index 8d0c33d..39dc642 100644 (file)
@@ -511,7 +511,7 @@ class MovePage {
                $logEntry->setComment( $reason );
                $logEntry->setParameters( [
                        '4::target' => $nt->getPrefixedText(),
-                       '5::noredir' => $redirectContent ? '0': '1',
+                       '5::noredir' => $redirectContent ? '0' : '1',
                ] );
 
                $formatter = LogFormatter::newFromEntry( $logEntry );
index 969171d..dd21194 100644 (file)
@@ -571,6 +571,7 @@ class OutputPage extends ContextSource {
         * @param bool $filter Whether to filter out insufficiently trustworthy modules
         * @param string|null $position If not null, only return modules with this position
         * @param string $param
+        * @param string $type
         * @return array Array of module names
         */
        public function getModules( $filter = false, $position = null, $param = 'mModules',
@@ -688,7 +689,7 @@ class OutputPage extends ContextSource {
         * Add one or more head items to the output
         *
         * @since 1.28
-        * @param string|string[] $value Raw HTML
+        * @param string|string[] $values Raw HTML
         */
        public function addHeadItems( $values ) {
                $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
@@ -1715,7 +1716,7 @@ class OutputPage extends ContextSource {
         * Add wikitext with a custom Title object
         *
         * @param string $text Wikitext
-        * @param Title $title
+        * @param Title &$title
         * @param bool $linestart Is this the start of a line?
         */
        public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
@@ -1726,7 +1727,7 @@ class OutputPage extends ContextSource {
         * Add wikitext with a custom Title object and tidy enabled.
         *
         * @param string $text Wikitext
-        * @param Title $title
+        * @param Title &$title
         * @param bool $linestart Is this the start of a line?
         */
        function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
@@ -1899,7 +1900,7 @@ class OutputPage extends ContextSource {
        /**
         * Add the output of a QuickTemplate to the output buffer
         *
-        * @param QuickTemplate $template
+        * @param QuickTemplate &$template
         */
        public function addTemplate( &$template ) {
                $this->addHTML( $template->getHTML() );
@@ -1963,7 +1964,7 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * @param $maxage
+        * @param int $maxage
         * @deprecated since 1.27 Use setCdnMaxage() instead
         */
        public function setSquidMaxage( $maxage ) {
@@ -1997,10 +1998,10 @@ class OutputPage extends ContextSource {
         * the TTL is higher the older the $mtime timestamp is. Essentially, the
         * TTL is 90% of the age of the object, subject to the min and max.
         *
-        * @param string|integer|float|bool|null $mtime Last-Modified timestamp
-        * @param integer $minTTL Mimimum TTL in seconds [default: 1 minute]
-        * @param integer $maxTTL Maximum TTL in seconds [default: $wgSquidMaxage]
-        * @return integer TTL in seconds
+        * @param string|int|float|bool|null $mtime Last-Modified timestamp
+        * @param int $minTTL Mimimum TTL in seconds [default: 1 minute]
+        * @param int $maxTTL Maximum TTL in seconds [default: $wgSquidMaxage]
+        * @return int TTL in seconds
         * @since 1.28
         */
        public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
@@ -2908,6 +2909,18 @@ class OutputPage extends ContextSource {
                $pieces[] = $this->buildExemptModules();
                $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
                $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
+
+               $min = ResourceLoader::inDebugMode() ? '' : '.min';
+               // Use an IE conditional comment to serve the script only to old IE
+               $pieces[] = '<!--[if lt IE 9]>' .
+                       Html::element( 'script', [
+                               'src' => self::transformResourcePath(
+                                       $this->getConfig(),
+                                       "/resources/lib/html5shiv/html5shiv{$min}.js"
+                               ),
+                       ] ) .
+                       '<![endif]-->';
+
                $pieces[] = Html::closeElement( 'head' );
 
                $bodyClasses = [];
@@ -3785,7 +3798,7 @@ class OutputPage extends ContextSource {
         * Caller is responsible for ensuring the file exists. Emits a PHP warning otherwise.
         *
         * @since 1.27
-        * @param string $remotePath URL path prefix that points to $localPath
+        * @param string $remotePathPrefix URL path prefix that points to $localPath
         * @param string $localPath File directory exposed at $remotePath
         * @param string $file Path to target file relative to $localPath
         * @return string URL
index 5a440c4..e9e271c 100644 (file)
@@ -36,6 +36,7 @@ class PHPVersionCheck {
                'ctype_digit' => 'ctype',
                'json_decode' => 'json',
                'iconv'       => 'iconv',
+               'mime_content_type' => 'fileinfo',
        );
 
        /**
@@ -97,7 +98,7 @@ class PHPVersionCheck {
                        'vendor' => 'the PHP Group',