From f6d65e533c62f6deb21342d4901ece24497b433e Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 4 Jun 2015 07:31:04 +0200 Subject: Update to MediaWiki 1.25.1 --- includes/AjaxDispatcher.php | 9 - includes/AutoLoader.php | 1194 +- includes/Autopromote.php | 4 +- includes/Block.php | 66 +- includes/Category.php | 11 - includes/CategoryFinder.php | 1 - includes/CategoryViewer.php | 119 +- includes/CdbCompat.php | 45 + includes/ChangeTags.php | 931 +- includes/Collation.php | 4 +- includes/Cookie.php | 291 - includes/DefaultSettings.php | 502 +- includes/Defines.php | 29 +- includes/EditPage.php | 469 +- includes/Export.php | 37 +- includes/Feed.php | 14 +- includes/FeedUtils.php | 7 +- includes/FileDeleteForm.php | 4 +- includes/GitInfo.php | 2 +- includes/GlobalFunctions.php | 723 +- includes/Hooks.php | 20 +- includes/Html.php | 192 +- includes/HtmlFormatter.php | 18 +- includes/HttpFunctions.php | 98 +- includes/Import.php | 377 +- includes/LinkFilter.php | 2 +- includes/Linker.php | 172 +- includes/MWNamespace.php | 4 +- includes/MWTimestamp.php | 9 +- includes/MagicWord.php | 12 +- includes/MediaWiki.php | 116 +- includes/MediaWikiVersionFetcher.php | 3 +- includes/Message.php | 79 +- includes/MessageBlobStore.php | 14 +- includes/MimeMagic.php | 14 +- includes/MovePage.php | 204 +- includes/NoLocalSettings.php | 63 + includes/OutputHandler.php | 3 +- includes/OutputPage.php | 266 +- includes/PHPVersionCheck.php | 157 + includes/PHPVersionError.php | 110 +- includes/Preferences.php | 102 +- includes/PrefixSearch.php | 161 +- includes/ProtectionForm.php | 69 +- includes/Revision.php | 125 +- includes/RevisionList.php | 27 +- includes/Sanitizer.php | 109 +- includes/Setup.php | 100 +- includes/SiteConfiguration.php | 4 + includes/SiteStats.php | 37 +- includes/StatCounter.php | 154 - includes/Status.php | 252 +- includes/StreamFile.php | 5 - includes/StubObject.php | 27 +- includes/TemplateParser.php | 201 + includes/TimestampException.php | 7 - includes/Title.php | 526 +- includes/TitleArray.php | 2 +- includes/User.php | 636 +- includes/UserArray.php | 23 +- includes/UserRightsProxy.php | 11 +- includes/WatchedItem.php | 10 +- includes/WebRequest.php | 66 +- includes/WebResponse.php | 42 +- includes/WebStart.php | 37 +- includes/WikiMap.php | 2 +- includes/Xml.php | 12 +- includes/ZhConversion.php | 13840 ++++++++++--------- includes/actions/Action.php | 24 + includes/actions/CreditsAction.php | 6 - includes/actions/DeleteAction.php | 3 +- includes/actions/EditAction.php | 2 +- includes/actions/FormAction.php | 4 +- includes/actions/HistoryAction.php | 59 +- includes/actions/InfoAction.php | 53 +- includes/actions/RawAction.php | 2 +- includes/actions/RevertAction.php | 4 +- includes/actions/RevisiondeleteAction.php | 6 + includes/actions/SpecialPageAction.php | 79 + includes/actions/UnwatchAction.php | 2 - includes/actions/WatchAction.php | 10 +- includes/api/ApiBase.php | 1122 +- includes/api/ApiBlock.php | 62 +- includes/api/ApiCheckToken.php | 81 + includes/api/ApiClearHasMsg.php | 9 +- includes/api/ApiComparePages.php | 25 +- includes/api/ApiContinuationManager.php | 238 + includes/api/ApiCreateAccount.php | 48 +- includes/api/ApiDelete.php | 29 +- includes/api/ApiDisabled.php | 16 +- includes/api/ApiEditPage.php | 143 +- includes/api/ApiEmailUser.php | 19 +- includes/api/ApiErrorFormatter.php | 303 + includes/api/ApiExpandTemplates.php | 85 +- includes/api/ApiFeedContributions.php | 54 +- includes/api/ApiFeedRecentChanges.php | 35 +- includes/api/ApiFeedWatchlist.php | 90 +- includes/api/ApiFileRevert.php | 22 +- includes/api/ApiFormatBase.php | 332 +- includes/api/ApiFormatDbg.php | 11 +- includes/api/ApiFormatDump.php | 11 +- includes/api/ApiFormatFeedWrapper.php | 30 +- includes/api/ApiFormatJson.php | 96 +- includes/api/ApiFormatNone.php | 4 - includes/api/ApiFormatPhp.php | 35 +- includes/api/ApiFormatRaw.php | 31 +- includes/api/ApiFormatTxt.php | 11 +- includes/api/ApiFormatWddx.php | 73 +- includes/api/ApiFormatXml.php | 302 +- includes/api/ApiFormatYaml.php | 4 +- includes/api/ApiHelp.php | 675 +- includes/api/ApiHelpParamValueMessage.php | 72 + includes/api/ApiImageRotate.php | 42 +- includes/api/ApiImport.php | 35 +- includes/api/ApiLogin.php | 35 +- includes/api/ApiLogout.php | 19 +- includes/api/ApiMain.php | 541 +- includes/api/ApiManageTags.php | 107 + includes/api/ApiMessage.php | 191 + includes/api/ApiMove.php | 97 +- includes/api/ApiOpenSearch.php | 349 +- includes/api/ApiOptions.php | 37 +- includes/api/ApiPageSet.php | 337 +- includes/api/ApiParamInfo.php | 455 +- includes/api/ApiParse.php | 294 +- includes/api/ApiPatrol.php | 21 +- includes/api/ApiProtect.php | 46 +- includes/api/ApiPurge.php | 39 +- includes/api/ApiQuery.php | 117 +- includes/api/ApiQueryAllCategories.php | 42 +- includes/api/ApiQueryAllDeletedRevisions.php | 427 + includes/api/ApiQueryAllImages.php | 99 +- includes/api/ApiQueryAllLinks.php | 95 +- includes/api/ApiQueryAllMessages.php | 43 +- includes/api/ApiQueryAllPages.php | 79 +- includes/api/ApiQueryAllUsers.php | 273 +- includes/api/ApiQueryBacklinks.php | 483 +- includes/api/ApiQueryBacklinksprop.php | 114 +- includes/api/ApiQueryBase.php | 42 +- includes/api/ApiQueryBlocks.php | 114 +- includes/api/ApiQueryCategories.php | 42 +- includes/api/ApiQueryCategoryInfo.php | 26 +- includes/api/ApiQueryCategoryMembers.php | 89 +- includes/api/ApiQueryContributors.php | 37 +- includes/api/ApiQueryDeletedRevisions.php | 304 + includes/api/ApiQueryDeletedrevs.php | 158 +- includes/api/ApiQueryDisabled.php | 14 +- includes/api/ApiQueryDuplicateFiles.php | 33 +- includes/api/ApiQueryExtLinksUsage.php | 60 +- includes/api/ApiQueryExternalLinks.php | 31 +- includes/api/ApiQueryFileRepoInfo.php | 27 +- includes/api/ApiQueryFilearchive.php | 69 +- includes/api/ApiQueryIWBacklinks.php | 39 +- includes/api/ApiQueryIWLinks.php | 51 +- includes/api/ApiQueryImageInfo.php | 121 +- includes/api/ApiQueryImages.php | 28 +- includes/api/ApiQueryInfo.php | 122 +- includes/api/ApiQueryLangBacklinks.php | 40 +- includes/api/ApiQueryLangLinks.php | 55 +- includes/api/ApiQueryLinks.php | 43 +- includes/api/ApiQueryLogEvents.php | 184 +- includes/api/ApiQueryORM.php | 11 +- includes/api/ApiQueryPagePropNames.php | 22 +- includes/api/ApiQueryPageProps.php | 22 +- includes/api/ApiQueryPagesWithProp.php | 39 +- includes/api/ApiQueryPrefixSearch.php | 40 +- includes/api/ApiQueryProtectedTitles.php | 40 +- includes/api/ApiQueryQueryPage.php | 28 +- includes/api/ApiQueryRandom.php | 27 +- includes/api/ApiQueryRecentChanges.php | 122 +- includes/api/ApiQueryRevisions.php | 616 +- includes/api/ApiQueryRevisionsBase.php | 477 + includes/api/ApiQuerySearch.php | 160 +- includes/api/ApiQuerySiteinfo.php | 260 +- includes/api/ApiQueryStashImageInfo.php | 52 +- includes/api/ApiQueryTags.php | 169 +- includes/api/ApiQueryTokens.php | 30 +- includes/api/ApiQueryUserContributions.php | 92 +- includes/api/ApiQueryUserInfo.php | 89 +- includes/api/ApiQueryUsers.php | 69 +- includes/api/ApiQueryWatchlist.php | 122 +- includes/api/ApiQueryWatchlistRaw.php | 36 +- includes/api/ApiResult.php | 1609 ++- includes/api/ApiRevisionDelete.php | 36 +- includes/api/ApiRollback.php | 36 +- includes/api/ApiRsd.php | 44 +- includes/api/ApiSerializable.php | 47 + includes/api/ApiSetNotificationTimestamp.php | 67 +- includes/api/ApiStashEdit.php | 412 + includes/api/ApiTag.php | 177 + includes/api/ApiTokens.php | 39 +- includes/api/ApiUnblock.php | 24 +- includes/api/ApiUndelete.php | 36 +- includes/api/ApiUpload.php | 138 +- includes/api/ApiUserrights.php | 56 +- includes/api/ApiWatch.php | 58 +- includes/api/i18n/ar.json | 28 + includes/api/i18n/av.json | 8 + includes/api/i18n/awa.json | 11 + includes/api/i18n/be-tarask.json | 55 + includes/api/i18n/bn.json | 8 + includes/api/i18n/bs.json | 16 + includes/api/i18n/ca.json | 43 + includes/api/i18n/ce.json | 12 + includes/api/i18n/cs.json | 223 + includes/api/i18n/cv.json | 8 + includes/api/i18n/de.json | 433 + includes/api/i18n/el.json | 12 + includes/api/i18n/en-gb.json | 156 + includes/api/i18n/en.json | 1169 ++ includes/api/i18n/es.json | 206 + includes/api/i18n/eu.json | 57 + includes/api/i18n/fa.json | 240 + includes/api/i18n/fi.json | 10 + includes/api/i18n/fr.json | 1054 ++ includes/api/i18n/frc.json | 19 + includes/api/i18n/fy.json | 15 + includes/api/i18n/gl.json | 1030 ++ includes/api/i18n/he.json | 180 + includes/api/i18n/hsb.json | 8 + includes/api/i18n/hu.json | 17 + includes/api/i18n/ia.json | 28 + includes/api/i18n/it.json | 31 + includes/api/i18n/ja.json | 213 + includes/api/i18n/jam.json | 8 + includes/api/i18n/ko.json | 36 + includes/api/i18n/ksh.json | 311 + includes/api/i18n/ku-latn.json | 9 + includes/api/i18n/lb.json | 94 + includes/api/i18n/ln.json | 8 + includes/api/i18n/lv.json | 8 + includes/api/i18n/lzh.json | 8 + includes/api/i18n/mg.json | 11 + includes/api/i18n/mk.json | 398 + includes/api/i18n/ms.json | 61 + includes/api/i18n/nap.json | 10 + includes/api/i18n/nb.json | 24 + includes/api/i18n/nds.json | 8 + includes/api/i18n/nl.json | 61 + includes/api/i18n/oc.json | 17 + includes/api/i18n/pa.json | 8 + includes/api/i18n/pam.json | 31 + includes/api/i18n/pl.json | 117 + includes/api/i18n/ps.json | 29 + includes/api/i18n/pt-br.json | 14 + includes/api/i18n/pt.json | 104 + includes/api/i18n/qqq.json | 1069 ++ includes/api/i18n/roa-tara.json | 11 + includes/api/i18n/ru.json | 55 + includes/api/i18n/si.json | 70 + includes/api/i18n/sr-ec.json | 24 + includes/api/i18n/sr-el.json | 11 + includes/api/i18n/sv.json | 372 + includes/api/i18n/te.json | 8 + includes/api/i18n/tl.json | 35 + includes/api/i18n/tr.json | 40 + includes/api/i18n/uk.json | 30 + includes/api/i18n/vi.json | 156 + includes/api/i18n/zh-hans.json | 782 ++ includes/api/i18n/zh-hant.json | 244 + includes/cache/BacklinkCache.php | 60 +- includes/cache/CacheDependency.php | 18 +- includes/cache/HTMLFileCache.php | 2 +- includes/cache/LinkBatch.php | 4 - includes/cache/LinkCache.php | 17 +- includes/cache/LocalisationCache.php | 71 +- includes/cache/MapCacheLRU.php | 123 - includes/cache/MessageCache.php | 36 +- includes/cache/ResourceFileCache.php | 9 +- includes/cache/UserCache.php | 2 - includes/cache/bloom/BloomCache.php | 323 - includes/cache/bloom/BloomCacheRedis.php | 370 - includes/cache/bloom/BloomFilters.php | 79 - includes/changes/ChangesFeed.php | 4 - includes/changes/ChangesList.php | 6 +- includes/changes/EnhancedChangesList.php | 213 +- includes/changes/OldChangesList.php | 9 +- includes/changes/RecentChange.php | 39 +- includes/changetags/ChangeTagsList.php | 77 + includes/changetags/ChangeTagsLogItem.php | 100 + includes/changetags/ChangeTagsLogList.php | 89 + includes/changetags/ChangeTagsRevisionItem.php | 58 + includes/changetags/ChangeTagsRevisionList.php | 99 + includes/config/ConfigException.php | 2 +- includes/config/GlobalVarConfig.php | 21 - includes/content/AbstractContent.php | 27 +- includes/content/CodeContentHandler.php | 1 + includes/content/Content.php | 7 +- includes/content/ContentHandler.php | 23 +- includes/content/JavaScriptContentHandler.php | 8 +- includes/content/JsonContent.php | 192 +- includes/content/JsonContentHandler.php | 24 +- includes/content/TextContent.php | 1 + includes/content/WikitextContent.php | 7 +- includes/context/ContextSource.php | 17 +- includes/context/DerivativeContext.php | 18 +- includes/context/IContextSource.php | 38 +- includes/context/RequestContext.php | 88 +- includes/db/Database.php | 856 +- includes/db/DatabaseError.php | 6 +- includes/db/DatabaseMssql.php | 86 +- includes/db/DatabaseMysql.php | 7 - includes/db/DatabaseMysqlBase.php | 125 +- includes/db/DatabaseMysqli.php | 14 +- includes/db/DatabaseOracle.php | 18 +- includes/db/DatabasePostgres.php | 23 +- includes/db/DatabaseSqlite.php | 163 +- includes/db/DatabaseUtility.php | 27 +- includes/db/IORMTable.php | 18 +- includes/db/LBFactory.php | 78 +- includes/db/LBFactoryMulti.php | 58 +- includes/db/LBFactorySingle.php | 20 +- includes/db/LoadBalancer.php | 390 +- includes/db/LoadMonitor.php | 4 +- includes/db/ORMTable.php | 50 +- includes/debug/MWDebug.php | 33 +- includes/debug/logger/LegacyLogger.php | 380 + includes/debug/logger/LegacySpi.php | 59 + includes/debug/logger/LoggerFactory.php | 121 + includes/debug/logger/MonologSpi.php | 251 + includes/debug/logger/NullSpi.php | 64 + includes/debug/logger/Spi.php | 47 + includes/debug/logger/monolog/LegacyFormatter.php | 48 + includes/debug/logger/monolog/LegacyHandler.php | 243 + includes/debug/logger/monolog/SyslogHandler.php | 96 + includes/debug/logger/monolog/WikiProcessor.php | 47 + includes/deferred/DeferredUpdates.php | 6 +- includes/deferred/HTMLCacheUpdate.php | 5 +- includes/deferred/LinksUpdate.php | 33 +- includes/deferred/SearchUpdate.php | 7 +- includes/deferred/SiteStatsUpdate.php | 9 +- includes/deferred/SqlDataUpdate.php | 85 +- includes/deferred/SquidUpdate.php | 43 - includes/deferred/ViewCountUpdate.php | 119 - includes/diff/DairikiDiff.php | 18 +- includes/diff/DiffFormatter.php | 4 - includes/diff/DifferenceEngine.php | 61 +- includes/diff/TableDiffFormatter.php | 4 +- includes/exception/HttpError.php | 42 +- includes/exception/MWException.php | 23 +- includes/exception/MWExceptionHandler.php | 201 +- includes/exception/TimestampException.php | 7 + includes/exception/UserNotLoggedIn.php | 10 +- includes/externalstore/ExternalStore.php | 2 +- includes/externalstore/ExternalStoreHttp.php | 2 +- includes/filebackend/FSFile.php | 7 - includes/filebackend/FSFileBackend.php | 8 + includes/filebackend/FileBackend.php | 4 +- includes/filebackend/FileBackendMultiWrite.php | 6 +- includes/filebackend/FileBackendStore.php | 87 +- includes/filebackend/FileOpBatch.php | 1 - includes/filebackend/SwiftFileBackend.php | 81 +- includes/filebackend/TempFSFile.php | 3 - includes/filebackend/filejournal/FileJournal.php | 4 +- includes/filebackend/lockmanager/DBLockManager.php | 4 +- includes/filebackend/lockmanager/LockManager.php | 10 +- .../filebackend/lockmanager/LockManagerGroup.php | 16 +- .../filebackend/lockmanager/MemcLockManager.php | 6 +- .../filebackend/lockmanager/RedisLockManager.php | 2 +- includes/filerepo/FileRepo.php | 64 +- includes/filerepo/FileRepoStatus.php | 36 +- includes/filerepo/ForeignAPIRepo.php | 2 +- includes/filerepo/RepoGroup.php | 6 +- includes/filerepo/file/ArchivedFile.php | 12 +- includes/filerepo/file/File.php | 51 +- includes/filerepo/file/LocalFile.php | 194 +- includes/filerepo/file/OldLocalFile.php | 14 +- includes/filerepo/file/UnregisteredLocalFile.php | 12 + includes/gallery/ImageGalleryBase.php | 20 +- includes/gallery/TraditionalImageGallery.php | 4 +- includes/htmlform/HTMLCheckField.php | 46 +- includes/htmlform/HTMLCheckMatrix.php | 14 +- includes/htmlform/HTMLFloatField.php | 2 +- includes/htmlform/HTMLForm.php | 191 +- includes/htmlform/HTMLFormField.php | 50 +- includes/htmlform/HTMLFormFieldCloner.php | 42 +- includes/htmlform/HTMLHiddenField.php | 18 +- includes/htmlform/HTMLIntField.php | 5 +- includes/htmlform/HTMLMultiSelectField.php | 8 + includes/htmlform/HTMLRadioField.php | 2 +- includes/htmlform/HTMLSelectAndOtherField.php | 11 +- includes/htmlform/HTMLSelectNamespace.php | 18 + includes/htmlform/HTMLTagFilter.php | 31 + includes/htmlform/HTMLTextField.php | 7 +- includes/htmlform/VFormHTMLForm.php | 141 + includes/installer/DatabaseInstaller.php | 15 +- includes/installer/DatabaseUpdater.php | 60 +- includes/installer/InstallDocFormatter.php | 34 +- includes/installer/Installer.php | 87 +- includes/installer/LocalSettingsGenerator.php | 39 +- includes/installer/MssqlInstaller.php | 4 +- includes/installer/MssqlUpdater.php | 5 + includes/installer/MysqlInstaller.php | 9 +- includes/installer/MysqlUpdater.php | 45 +- includes/installer/OracleUpdater.php | 6 +- includes/installer/PostgresInstaller.php | 16 +- includes/installer/PostgresUpdater.php | 6 +- includes/installer/SqliteInstaller.php | 72 +- includes/installer/SqliteUpdater.php | 13 +- includes/installer/WebInstaller.php | 27 +- includes/installer/WebInstallerOutput.php | 37 +- includes/installer/WebInstallerPage.php | 21 +- includes/installer/i18n/ar.json | 8 +- includes/installer/i18n/ast.json | 9 +- includes/installer/i18n/av.json | 5 +- includes/installer/i18n/az.json | 2 +- includes/installer/i18n/ba.json | 21 +- includes/installer/i18n/be-tarask.json | 2 +- includes/installer/i18n/bg.json | 11 +- includes/installer/i18n/bgn.json | 26 + includes/installer/i18n/bn.json | 13 +- includes/installer/i18n/bs.json | 33 +- includes/installer/i18n/ca.json | 49 +- includes/installer/i18n/ckb.json | 19 +- includes/installer/i18n/cs.json | 2 +- includes/installer/i18n/cv.json | 10 +- includes/installer/i18n/cy.json | 3 + includes/installer/i18n/de.json | 24 +- includes/installer/i18n/diq.json | 2 +- includes/installer/i18n/el.json | 12 +- includes/installer/i18n/eml.json | 16 + includes/installer/i18n/en-gb.json | 6 +- includes/installer/i18n/en.json | 9 +- includes/installer/i18n/es.json | 142 +- includes/installer/i18n/et.json | 16 +- includes/installer/i18n/eu.json | 5 +- includes/installer/i18n/fa.json | 40 +- includes/installer/i18n/fi.json | 51 +- includes/installer/i18n/fr.json | 5 +- includes/installer/i18n/frc.json | 21 +- includes/installer/i18n/fy.json | 14 +- includes/installer/i18n/gl.json | 2 +- includes/installer/i18n/gor.json | 48 + includes/installer/i18n/he.json | 2 +- includes/installer/i18n/hi.json | 16 +- includes/installer/i18n/hu.json | 13 +- includes/installer/i18n/ia.json | 13 +- includes/installer/i18n/id.json | 6 +- includes/installer/i18n/it.json | 7 +- includes/installer/i18n/ja.json | 1 + includes/installer/i18n/ko.json | 76 +- includes/installer/i18n/ksh.json | 31 +- includes/installer/i18n/ku-latn.json | 1 + includes/installer/i18n/lb.json | 3 +- includes/installer/i18n/lzh.json | 1 + includes/installer/i18n/mai.json | 26 +- includes/installer/i18n/mfe.json | 45 + includes/installer/i18n/mk.json | 7 +- includes/installer/i18n/ms.json | 11 +- includes/installer/i18n/nap.json | 196 +- includes/installer/i18n/nb.json | 26 +- includes/installer/i18n/ne.json | 6 + includes/installer/i18n/nl.json | 28 +- includes/installer/i18n/oc.json | 3 + includes/installer/i18n/pa.json | 24 +- includes/installer/i18n/pl.json | 7 +- includes/installer/i18n/pms.json | 10 +- includes/installer/i18n/pt-br.json | 8 +- includes/installer/i18n/pt.json | 2 +- includes/installer/i18n/qqq.json | 3 +- includes/installer/i18n/ro.json | 22 +- includes/installer/i18n/roa-tara.json | 15 +- includes/installer/i18n/ru.json | 9 +- includes/installer/i18n/sco.json | 13 + includes/installer/i18n/si.json | 4 +- includes/installer/i18n/sk.json | 5 +- includes/installer/i18n/sl.json | 16 + includes/installer/i18n/sr-ec.json | 17 +- includes/installer/i18n/sr-el.json | 1 + includes/installer/i18n/su.json | 8 +- includes/installer/i18n/sv.json | 8 +- includes/installer/i18n/te.json | 2 +- includes/installer/i18n/th.json | 90 +- includes/installer/i18n/tl.json | 2 - includes/installer/i18n/tr.json | 7 +- includes/installer/i18n/uk.json | 2 + includes/installer/i18n/vi.json | 137 +- includes/installer/i18n/yi.json | 5 +- includes/installer/i18n/zh-hans.json | 11 +- includes/installer/i18n/zh-hant.json | 23 +- includes/interwiki/Interwiki.php | 4 +- includes/jobqueue/Job.php | 120 +- includes/jobqueue/JobQueue.php | 83 +- includes/jobqueue/JobQueueDB.php | 6 +- includes/jobqueue/JobQueueFederated.php | 131 +- includes/jobqueue/JobQueueGroup.php | 14 +- includes/jobqueue/JobQueueRedis.php | 255 +- includes/jobqueue/JobRunner.php | 86 +- includes/jobqueue/JobSpecification.php | 36 +- .../jobqueue/aggregator/JobQueueAggregator.php | 28 +- .../jobqueue/aggregator/JobQueueAggregatorMemc.php | 125 - .../aggregator/JobQueueAggregatorRedis.php | 2 +- includes/jobqueue/jobs/AssembleUploadChunksJob.php | 19 +- includes/jobqueue/jobs/DuplicateJob.php | 2 +- includes/jobqueue/jobs/EnqueueJob.php | 88 + includes/jobqueue/jobs/HTMLCacheUpdateJob.php | 46 +- includes/jobqueue/jobs/NullJob.php | 2 +- includes/jobqueue/jobs/PublishStashedFileJob.php | 22 +- includes/jobqueue/jobs/RecentChangesUpdateJob.php | 223 + includes/jobqueue/jobs/RefreshLinksJob.php | 16 +- includes/jobqueue/jobs/RefreshLinksJob2.php | 141 - includes/jobqueue/jobs/ThumbnailRenderJob.php | 109 + includes/jobqueue/jobs/UploadFromUrlJob.php | 2 +- includes/json/FormatJson.php | 102 +- includes/libs/APACHE-LICENSE-2.0.txt | 202 + includes/libs/ArrayUtils.php | 187 + includes/libs/BufferingStatsdDataFactory.php | 59 + includes/libs/CSSJanus.php | 458 - includes/libs/CSSMin.php | 151 +- includes/libs/Cookie.php | 291 + includes/libs/DeferredStringifier.php | 57 + includes/libs/ExplodeIterator.php | 116 + includes/libs/GenericArrayObject.php | 6 +- includes/libs/IPSet.php | 3 +- includes/libs/MapCacheLRU.php | 131 + includes/libs/MessageSpecifier.php | 39 + includes/libs/MultiHttpClient.php | 28 +- includes/libs/ObjectFactory.php | 93 + includes/libs/ProcessCacheLRU.php | 39 +- includes/libs/ReplacementArray.php | 125 + includes/libs/RunningStat.php | 8 +- includes/libs/ScopedCallback.php | 12 +- includes/libs/StatusValue.php | 316 + includes/libs/StringUtils.php | 317 + includes/libs/UDPTransport.php | 102 + includes/libs/Xhprof.php | 445 + includes/libs/XmlTypeCheck.php | 2 +- includes/libs/composer/ComposerJson.php | 54 + includes/libs/composer/ComposerLock.php | 38 + includes/libs/jsminplus.php | 2 +- includes/libs/lessc.inc.php | 3796 ----- includes/libs/normal/UtfNormal.php | 129 + includes/libs/normal/UtfNormalDefines.php | 186 + includes/libs/normal/UtfNormalUtil.php | 99 + includes/libs/objectcache/APCBagOStuff.php | 69 + includes/libs/objectcache/BagOStuff.php | 438 + includes/libs/objectcache/EmptyBagOStuff.php | 45 + includes/libs/objectcache/HashBagOStuff.php | 87 + includes/libs/objectcache/WinCacheBagOStuff.php | 99 + includes/libs/objectcache/XCacheBagOStuff.php | 89 + includes/libs/replacers/DoubleReplacer.php | 43 + includes/libs/replacers/HashtableReplacer.php | 44 + includes/libs/replacers/RegexlikeReplacer.php | 46 + includes/libs/replacers/Replacer.php | 38 + .../libs/virtualrest/ParsoidVirtualRESTService.php | 126 + .../virtualrest/RestbaseVirtualRESTService.php | 177 + .../libs/virtualrest/VirtualRESTServiceClient.php | 31 +- includes/logging/BlockLogFormatter.php | 224 + includes/logging/DeleteLogFormatter.php | 78 +- includes/logging/LogEntry.php | 38 +- includes/logging/LogEventsList.php | 76 +- includes/logging/LogFormatter.php | 253 +- includes/logging/LogPage.php | 156 +- includes/logging/LogPager.php | 2 - includes/logging/MergeLogFormatter.php | 91 + includes/logging/MoveLogFormatter.php | 29 +- includes/logging/PatrolLogFormatter.php | 20 + includes/logging/RightsLogFormatter.php | 65 +- includes/logging/TagLogFormatter.php | 49 + includes/logging/UploadLogFormatter.php | 49 + includes/mail/EmailNotification.php | 10 +- includes/mail/MailAddress.php | 2 +- includes/mail/UserMailer.php | 4 +- includes/media/BMP.php | 2 +- includes/media/Bitmap.php | 14 +- includes/media/BitmapMetadataHandler.php | 2 +- includes/media/DjVu.php | 5 +- includes/media/DjVuImage.php | 16 +- includes/media/Exif.php | 2 +- includes/media/ExifBitmap.php | 5 +- includes/media/FormatMetadata.php | 98 +- includes/media/GIF.php | 5 +- includes/media/GIFMetadataExtractor.php | 2 +- includes/media/IPTC.php | 2 +- includes/media/ImageHandler.php | 6 +- includes/media/Jpeg.php | 4 +- includes/media/JpegMetadataExtractor.php | 4 +- includes/media/MediaHandler.php | 34 +- .../MediaTransformInvalidParametersException.php | 26 + includes/media/MediaTransformOutput.php | 40 +- includes/media/PNG.php | 10 +- includes/media/SVG.php | 9 +- includes/media/SVGMetadataExtractor.php | 20 +- includes/media/Tiff.php | 2 +- includes/media/TransformationalImageHandler.php | 75 +- includes/media/XCF.php | 2 +- includes/media/XMP.php | 13 +- includes/media/XMPInfo.php | 2 +- includes/mime.info | 1 + includes/mime.types | 3 +- includes/normal/Makefile | 78 - includes/normal/README | 59 - includes/normal/RandomTest.php | 102 - includes/normal/Utf8Test.php | 156 - includes/normal/UtfNormal.php | 790 -- includes/normal/UtfNormalBench.php | 105 - includes/normal/UtfNormalData.inc | 14 - includes/normal/UtfNormalDataK.inc | 11 - includes/normal/UtfNormalDefines.php | 77 - includes/normal/UtfNormalGenerate.php | 250 - includes/normal/UtfNormalMemStress.php | 107 - includes/normal/UtfNormalTest.php | 257 - includes/normal/UtfNormalTest2.php | 275 - includes/normal/UtfNormalUtil.php | 154 - includes/objectcache/APCBagOStuff.php | 108 - includes/objectcache/BagOStuff.php | 406 - includes/objectcache/EmptyBagOStuff.php | 80 - includes/objectcache/HashBagOStuff.php | 113 - includes/objectcache/MemcachedBagOStuff.php | 19 +- includes/objectcache/MemcachedClient.php | 20 +- includes/objectcache/MemcachedPeclBagOStuff.php | 25 +- includes/objectcache/MemcachedPhpBagOStuff.php | 18 +- includes/objectcache/MultiWriteBagOStuff.php | 30 +- includes/objectcache/ObjectCache.php | 11 +- includes/objectcache/ObjectCacheSessionHandler.php | 38 +- includes/objectcache/RedisBagOStuff.php | 24 +- includes/objectcache/SqlBagOStuff.php | 75 +- includes/objectcache/WinCacheBagOStuff.php | 92 - includes/objectcache/XCacheBagOStuff.php | 117 - includes/page/Article.php | 350 +- includes/page/CategoryPage.php | 6 +- includes/page/ImagePage.php | 100 +- includes/page/WikiPage.php | 476 +- includes/pager/IndexPager.php | 5 +- includes/parser/CacheTime.php | 26 +- includes/parser/CoreParserFunctions.php | 92 +- includes/parser/CoreTagHooks.php | 27 + includes/parser/DateFormatter.php | 15 +- includes/parser/LinkHolderArray.php | 25 +- includes/parser/MWTidy.php | 76 +- includes/parser/Parser.php | 686 +- includes/parser/ParserCache.php | 7 - includes/parser/ParserOptions.php | 97 +- includes/parser/ParserOutput.php | 141 +- includes/parser/Preprocessor_DOM.php | 30 +- includes/parser/Preprocessor_Hash.php | 20 +- includes/parser/StripState.php | 2 - includes/password/PasswordFactory.php | 15 +- includes/poolcounter/PoolCounter.php | 57 +- includes/poolcounter/PoolCounterRedis.php | 17 +- includes/poolcounter/PoolWorkArticleView.php | 7 +- includes/profiler/ProfileSection.php | 43 + includes/profiler/Profiler.php | 499 +- includes/profiler/ProfilerFunctions.php | 56 + includes/profiler/ProfilerMwprof.php | 256 - includes/profiler/ProfilerSectionOnly.php | 104 + includes/profiler/ProfilerSimpleDB.php | 111 - includes/profiler/ProfilerSimpleText.php | 82 - includes/profiler/ProfilerSimpleTrace.php | 85 - includes/profiler/ProfilerSimpleUDP.php | 75 - includes/profiler/ProfilerStandard.php | 559 - includes/profiler/ProfilerStub.php | 25 +- includes/profiler/ProfilerXhprof.php | 194 + includes/profiler/SectionProfiler.php | 530 + includes/profiler/TransactionProfiler.php | 274 + includes/profiler/output/ProfilerOutput.php | 57 + includes/profiler/output/ProfilerOutputDb.php | 94 + includes/profiler/output/ProfilerOutputDump.php | 51 + includes/profiler/output/ProfilerOutputStats.php | 57 + includes/profiler/output/ProfilerOutputText.php | 77 + includes/profiler/output/ProfilerOutputUdp.php | 96 + includes/rcfeed/IRCColourfulRCFeedFormatter.php | 2 +- includes/rcfeed/UDPRCFeedEngine.php | 3 +- includes/registration/ExtensionProcessor.php | 299 + includes/registration/ExtensionRegistry.php | 254 + includes/registration/Processor.php | 27 + .../DerivativeResourceLoaderContext.php | 1 + includes/resourceloader/ResourceLoader.php | 323 +- includes/resourceloader/ResourceLoaderContext.php | 91 + .../ResourceLoaderEditToolbarModule.php | 1 + .../resourceloader/ResourceLoaderFileModule.php | 109 +- .../ResourceLoaderFilePageModule.php | 37 - includes/resourceloader/ResourceLoaderImage.php | 388 + .../resourceloader/ResourceLoaderImageModule.php | 327 + .../ResourceLoaderLanguageDataModule.php | 12 +- .../ResourceLoaderLanguageNamesModule.php | 14 +- includes/resourceloader/ResourceLoaderModule.php | 87 +- .../ResourceLoaderNoscriptModule.php | 54 - .../resourceloader/ResourceLoaderSiteModule.php | 13 +- .../resourceloader/ResourceLoaderSkinModule.php | 92 + .../ResourceLoaderSpecialCharacterDataModule.php | 107 + .../resourceloader/ResourceLoaderStartUpModule.php | 118 +- .../ResourceLoaderUserCSSPrefsModule.php | 14 +- .../ResourceLoaderUserDefaultsModule.php | 62 + .../ResourceLoaderUserGroupsModule.php | 26 +- .../resourceloader/ResourceLoaderUserModule.php | 36 +- .../ResourceLoaderUserOptionsModule.php | 15 +- .../ResourceLoaderUserTokensModule.php | 13 +- .../resourceloader/ResourceLoaderWikiModule.php | 90 +- includes/revisiondelete/RevDelArchiveList.php | 2 +- includes/revisiondelete/RevDelArchivedFileList.php | 2 +- includes/revisiondelete/RevDelFileList.php | 2 +- includes/revisiondelete/RevDelList.php | 28 +- includes/revisiondelete/RevDelLogItem.php | 10 +- includes/revisiondelete/RevDelLogList.php | 8 +- includes/revisiondelete/RevDelRevisionList.php | 4 +- includes/revisiondelete/RevisionDeleteUser.php | 4 +- includes/revisiondelete/RevisionDeleter.php | 3 +- includes/search/SearchEngine.php | 64 +- includes/search/SearchHighlighter.php | 26 +- includes/search/SearchMySQL.php | 4 - includes/search/SearchPostgres.php | 2 +- includes/search/SearchResult.php | 9 +- includes/site/CachingSiteStore.php | 195 + includes/site/DBSiteStore.php | 345 + includes/site/FileBasedSiteLookup.php | 139 + includes/site/HashSiteStore.php | 123 + includes/site/MediaWikiSite.php | 4 +- includes/site/SiteExporter.php | 114 + includes/site/SiteImporter.php | 263 + includes/site/SiteLookup.php | 50 + includes/site/SiteSQLStore.php | 459 +- includes/site/SiteStore.php | 29 +- includes/site/SitesCacheFileBuilder.php | 113 + includes/skins/BaseTemplate.php | 644 + includes/skins/MediaWikiI18N.php | 52 + includes/skins/QuickTemplate.php | 194 + includes/skins/Skin.php | 176 +- includes/skins/SkinApi.php | 71 + includes/skins/SkinApiTemplate.php | 63 + includes/skins/SkinFactory.php | 110 +- includes/skins/SkinFallbackTemplate.php | 29 +- includes/skins/SkinTemplate.php | 908 +- includes/specialpage/ChangesListSpecialPage.php | 18 +- includes/specialpage/FormSpecialPage.php | 29 +- includes/specialpage/ImageQueryPage.php | 4 +- includes/specialpage/PageQueryPage.php | 2 +- includes/specialpage/QueryPage.php | 17 +- includes/specialpage/RedirectSpecialPage.php | 2 +- includes/specialpage/SpecialPage.php | 55 +- includes/specialpage/SpecialPageFactory.php | 40 +- includes/specialpage/WantedQueryPage.php | 4 +- includes/specials/SpecialActiveusers.php | 174 +- includes/specials/SpecialAllMessages.php | 14 +- includes/specials/SpecialAllPages.php | 5 +- includes/specials/SpecialApiHelp.php | 93 + includes/specials/SpecialBlock.php | 54 +- includes/specials/SpecialBlockList.php | 17 +- includes/specials/SpecialBooksources.php | 23 +- includes/specials/SpecialCategories.php | 6 +- includes/specials/SpecialChangeEmail.php | 34 +- includes/specials/SpecialChangePassword.php | 14 +- includes/specials/SpecialConfirmemail.php | 3 + includes/specials/SpecialContributions.php | 59 +- includes/specials/SpecialDeletedContributions.php | 113 +- includes/specials/SpecialDiff.php | 1 + includes/specials/SpecialEditTags.php | 460 + includes/specials/SpecialEditWatchlist.php | 42 +- includes/specials/SpecialEmailuser.php | 12 +- includes/specials/SpecialExpandTemplates.php | 5 +- includes/specials/SpecialExport.php | 9 +- includes/specials/SpecialFileDuplicateSearch.php | 52 +- includes/specials/SpecialFilepath.php | 1 - includes/specials/SpecialImport.php | 43 +- includes/specials/SpecialJavaScriptTest.php | 33 +- includes/specials/SpecialLinkSearch.php | 52 +- includes/specials/SpecialListDuplicatedFiles.php | 2 +- includes/specials/SpecialListfiles.php | 12 +- includes/specials/SpecialListgrouprights.php | 78 +- includes/specials/SpecialListredirects.php | 2 +- includes/specials/SpecialListusers.php | 71 +- includes/specials/SpecialLog.php | 115 +- includes/specials/SpecialLonelypages.php | 2 +- includes/specials/SpecialMediaStatistics.php | 22 +- includes/specials/SpecialMergeHistory.php | 27 +- includes/specials/SpecialMostcategories.php | 2 +- includes/specials/SpecialMostimages.php | 2 +- includes/specials/SpecialMostinterwikis.php | 2 +- includes/specials/SpecialMostlinked.php | 2 +- includes/specials/SpecialMostlinkedcategories.php | 2 +- includes/specials/SpecialMostlinkedtemplates.php | 2 +- includes/specials/SpecialMovepage.php | 226 +- includes/specials/SpecialMyLanguage.php | 5 + includes/specials/SpecialNewimages.php | 14 +- includes/specials/SpecialNewpages.php | 135 +- includes/specials/SpecialPageLanguage.php | 8 +- includes/specials/SpecialPagesWithProp.php | 49 +- includes/specials/SpecialPasswordReset.php | 15 +- includes/specials/SpecialPopularpages.php | 89 - includes/specials/SpecialPreferences.php | 2 + includes/specials/SpecialPrefixindex.php | 5 +- includes/specials/SpecialProtectedpages.php | 17 +- includes/specials/SpecialProtectedtitles.php | 14 +- includes/specials/SpecialRandomInCategory.php | 10 +- includes/specials/SpecialRandompage.php | 2 +- includes/specials/SpecialRecentchanges.php | 23 +- includes/specials/SpecialRedirect.php | 15 +- includes/specials/SpecialResetTokens.php | 2 +- includes/specials/SpecialRevisiondelete.php | 34 +- includes/specials/SpecialRunJobs.php | 11 +- includes/specials/SpecialSearch.php | 131 +- includes/specials/SpecialShortpages.php | 2 +- includes/specials/SpecialSpecialpages.php | 1 + includes/specials/SpecialStatistics.php | 100 +- includes/specials/SpecialTags.php | 336 +- includes/specials/SpecialTrackingCategories.php | 142 +- includes/specials/SpecialUnblock.php | 11 +- includes/specials/SpecialUndelete.php | 32 +- includes/specials/SpecialUpload.php | 99 +- includes/specials/SpecialUploadStash.php | 14 +- includes/specials/SpecialUserlogin.php | 103 +- includes/specials/SpecialUserlogout.php | 2 +- includes/specials/SpecialUserrights.php | 65 +- includes/specials/SpecialVersion.php | 151 +- includes/specials/SpecialWantedcategories.php | 7 +- includes/specials/SpecialWantedfiles.php | 4 +- includes/specials/SpecialWantedpages.php | 7 +- includes/specials/SpecialWatchlist.php | 60 +- includes/specials/SpecialWhatlinkshere.php | 67 +- includes/templates/NoLocalSettings.mustache | 39 + includes/templates/NoLocalSettings.php | 97 - includes/templates/Usercreate.php | 25 +- includes/templates/Userlogin.php | 75 +- includes/title/ForeignTitle.php | 117 + includes/title/ForeignTitleFactory.php | 36 + includes/title/ImportTitleFactory.php | 36 + includes/title/MalformedTitleException.php | 1 + includes/title/MediaWikiPageLinkRenderer.php | 1 + includes/title/MediaWikiTitleCodec.php | 34 +- includes/title/NaiveForeignTitleFactory.php | 71 + includes/title/NaiveImportTitleFactory.php | 65 + .../title/NamespaceAwareForeignTitleFactory.php | 134 + includes/title/NamespaceImportTitleFactory.php | 52 + includes/title/PageLinkRenderer.php | 1 + includes/title/SubpageImportTitleFactory.php | 55 + includes/title/TitleFormatter.php | 1 + includes/title/TitleParser.php | 1 + includes/title/TitleValue.php | 1 + includes/upload/UploadBase.php | 161 +- includes/upload/UploadFromChunks.php | 20 +- includes/upload/UploadFromUrl.php | 26 +- includes/upload/UploadStash.php | 4 +- includes/utils/ArrayUtils.php | 187 - includes/utils/AutoloadGenerator.php | 296 + includes/utils/Cdb.php | 163 - includes/utils/CdbDBA.php | 75 - includes/utils/CdbPHP.php | 494 - includes/utils/IP.php | 23 +- includes/utils/MWCryptHKDF.php | 11 +- includes/utils/MWCryptRand.php | 11 - includes/utils/MWFunction.php | 37 +- includes/utils/StringUtils.php | 612 - includes/utils/UIDGenerator.php | 6 +- includes/utils/ZipDirectoryReader.php | 1 + 844 files changed, 55986 insertions(+), 36888 deletions(-) create mode 100644 includes/CdbCompat.php delete mode 100644 includes/Cookie.php create mode 100644 includes/NoLocalSettings.php create mode 100644 includes/PHPVersionCheck.php delete mode 100644 includes/StatCounter.php create mode 100644 includes/TemplateParser.php delete mode 100644 includes/TimestampException.php create mode 100644 includes/actions/SpecialPageAction.php create mode 100644 includes/api/ApiCheckToken.php create mode 100644 includes/api/ApiContinuationManager.php create mode 100644 includes/api/ApiErrorFormatter.php create mode 100644 includes/api/ApiHelpParamValueMessage.php create mode 100644 includes/api/ApiManageTags.php create mode 100644 includes/api/ApiMessage.php create mode 100644 includes/api/ApiQueryAllDeletedRevisions.php create mode 100644 includes/api/ApiQueryDeletedRevisions.php create mode 100644 includes/api/ApiQueryRevisionsBase.php create mode 100644 includes/api/ApiSerializable.php create mode 100644 includes/api/ApiStashEdit.php create mode 100644 includes/api/ApiTag.php create mode 100644 includes/api/i18n/ar.json create mode 100644 includes/api/i18n/av.json create mode 100644 includes/api/i18n/awa.json create mode 100644 includes/api/i18n/be-tarask.json create mode 100644 includes/api/i18n/bn.json create mode 100644 includes/api/i18n/bs.json create mode 100644 includes/api/i18n/ca.json create mode 100644 includes/api/i18n/ce.json create mode 100644 includes/api/i18n/cs.json create mode 100644 includes/api/i18n/cv.json create mode 100644 includes/api/i18n/de.json create mode 100644 includes/api/i18n/el.json create mode 100644 includes/api/i18n/en-gb.json create mode 100644 includes/api/i18n/en.json create mode 100644 includes/api/i18n/es.json create mode 100644 includes/api/i18n/eu.json create mode 100644 includes/api/i18n/fa.json create mode 100644 includes/api/i18n/fi.json create mode 100644 includes/api/i18n/fr.json create mode 100644 includes/api/i18n/frc.json create mode 100644 includes/api/i18n/fy.json create mode 100644 includes/api/i18n/gl.json create mode 100644 includes/api/i18n/he.json create mode 100644 includes/api/i18n/hsb.json create mode 100644 includes/api/i18n/hu.json create mode 100644 includes/api/i18n/ia.json create mode 100644 includes/api/i18n/it.json create mode 100644 includes/api/i18n/ja.json create mode 100644 includes/api/i18n/jam.json create mode 100644 includes/api/i18n/ko.json create mode 100644 includes/api/i18n/ksh.json create mode 100644 includes/api/i18n/ku-latn.json create mode 100644 includes/api/i18n/lb.json create mode 100644 includes/api/i18n/ln.json create mode 100644 includes/api/i18n/lv.json create mode 100644 includes/api/i18n/lzh.json create mode 100644 includes/api/i18n/mg.json create mode 100644 includes/api/i18n/mk.json create mode 100644 includes/api/i18n/ms.json create mode 100644 includes/api/i18n/nap.json create mode 100644 includes/api/i18n/nb.json create mode 100644 includes/api/i18n/nds.json create mode 100644 includes/api/i18n/nl.json create mode 100644 includes/api/i18n/oc.json create mode 100644 includes/api/i18n/pa.json create mode 100644 includes/api/i18n/pam.json create mode 100644 includes/api/i18n/pl.json create mode 100644 includes/api/i18n/ps.json create mode 100644 includes/api/i18n/pt-br.json create mode 100644 includes/api/i18n/pt.json create mode 100644 includes/api/i18n/qqq.json create mode 100644 includes/api/i18n/roa-tara.json create mode 100644 includes/api/i18n/ru.json create mode 100644 includes/api/i18n/si.json create mode 100644 includes/api/i18n/sr-ec.json create mode 100644 includes/api/i18n/sr-el.json create mode 100644 includes/api/i18n/sv.json create mode 100644 includes/api/i18n/te.json create mode 100644 includes/api/i18n/tl.json create mode 100644 includes/api/i18n/tr.json create mode 100644 includes/api/i18n/uk.json create mode 100644 includes/api/i18n/vi.json create mode 100644 includes/api/i18n/zh-hans.json create mode 100644 includes/api/i18n/zh-hant.json delete mode 100644 includes/cache/MapCacheLRU.php delete mode 100644 includes/cache/bloom/BloomCache.php delete mode 100644 includes/cache/bloom/BloomCacheRedis.php delete mode 100644 includes/cache/bloom/BloomFilters.php create mode 100644 includes/changetags/ChangeTagsList.php create mode 100644 includes/changetags/ChangeTagsLogItem.php create mode 100644 includes/changetags/ChangeTagsLogList.php create mode 100644 includes/changetags/ChangeTagsRevisionItem.php create mode 100644 includes/changetags/ChangeTagsRevisionList.php create mode 100644 includes/debug/logger/LegacyLogger.php create mode 100644 includes/debug/logger/LegacySpi.php create mode 100644 includes/debug/logger/LoggerFactory.php create mode 100644 includes/debug/logger/MonologSpi.php create mode 100644 includes/debug/logger/NullSpi.php create mode 100644 includes/debug/logger/Spi.php create mode 100644 includes/debug/logger/monolog/LegacyFormatter.php create mode 100644 includes/debug/logger/monolog/LegacyHandler.php create mode 100644 includes/debug/logger/monolog/SyslogHandler.php create mode 100644 includes/debug/logger/monolog/WikiProcessor.php delete mode 100644 includes/deferred/ViewCountUpdate.php create mode 100644 includes/exception/TimestampException.php create mode 100644 includes/htmlform/HTMLSelectNamespace.php create mode 100644 includes/htmlform/HTMLTagFilter.php create mode 100644 includes/htmlform/VFormHTMLForm.php create mode 100644 includes/installer/i18n/bgn.json create mode 100644 includes/installer/i18n/eml.json create mode 100644 includes/installer/i18n/gor.json create mode 100644 includes/installer/i18n/mfe.json delete mode 100644 includes/jobqueue/aggregator/JobQueueAggregatorMemc.php create mode 100644 includes/jobqueue/jobs/EnqueueJob.php create mode 100644 includes/jobqueue/jobs/RecentChangesUpdateJob.php delete mode 100644 includes/jobqueue/jobs/RefreshLinksJob2.php create mode 100644 includes/jobqueue/jobs/ThumbnailRenderJob.php create mode 100644 includes/libs/APACHE-LICENSE-2.0.txt create mode 100644 includes/libs/ArrayUtils.php create mode 100644 includes/libs/BufferingStatsdDataFactory.php delete mode 100644 includes/libs/CSSJanus.php create mode 100644 includes/libs/Cookie.php create mode 100644 includes/libs/DeferredStringifier.php create mode 100644 includes/libs/ExplodeIterator.php create mode 100644 includes/libs/MapCacheLRU.php create mode 100644 includes/libs/MessageSpecifier.php create mode 100644 includes/libs/ObjectFactory.php create mode 100644 includes/libs/ReplacementArray.php create mode 100644 includes/libs/StatusValue.php create mode 100644 includes/libs/StringUtils.php create mode 100644 includes/libs/UDPTransport.php create mode 100644 includes/libs/Xhprof.php create mode 100644 includes/libs/composer/ComposerJson.php create mode 100644 includes/libs/composer/ComposerLock.php delete mode 100644 includes/libs/lessc.inc.php create mode 100644 includes/libs/normal/UtfNormal.php create mode 100644 includes/libs/normal/UtfNormalDefines.php create mode 100644 includes/libs/normal/UtfNormalUtil.php create mode 100644 includes/libs/objectcache/APCBagOStuff.php create mode 100644 includes/libs/objectcache/BagOStuff.php create mode 100644 includes/libs/objectcache/EmptyBagOStuff.php create mode 100644 includes/libs/objectcache/HashBagOStuff.php create mode 100644 includes/libs/objectcache/WinCacheBagOStuff.php create mode 100644 includes/libs/objectcache/XCacheBagOStuff.php create mode 100644 includes/libs/replacers/DoubleReplacer.php create mode 100644 includes/libs/replacers/HashtableReplacer.php create mode 100644 includes/libs/replacers/RegexlikeReplacer.php create mode 100644 includes/libs/replacers/Replacer.php create mode 100644 includes/libs/virtualrest/ParsoidVirtualRESTService.php create mode 100644 includes/libs/virtualrest/RestbaseVirtualRESTService.php create mode 100644 includes/logging/BlockLogFormatter.php create mode 100644 includes/logging/MergeLogFormatter.php create mode 100644 includes/logging/TagLogFormatter.php create mode 100644 includes/logging/UploadLogFormatter.php create mode 100644 includes/media/MediaTransformInvalidParametersException.php delete mode 100644 includes/normal/Makefile delete mode 100644 includes/normal/README delete mode 100644 includes/normal/RandomTest.php delete mode 100644 includes/normal/Utf8Test.php delete mode 100644 includes/normal/UtfNormal.php delete mode 100644 includes/normal/UtfNormalBench.php delete mode 100644 includes/normal/UtfNormalData.inc delete mode 100644 includes/normal/UtfNormalDataK.inc delete mode 100644 includes/normal/UtfNormalDefines.php delete mode 100644 includes/normal/UtfNormalGenerate.php delete mode 100644 includes/normal/UtfNormalMemStress.php delete mode 100644 includes/normal/UtfNormalTest.php delete mode 100644 includes/normal/UtfNormalTest2.php delete mode 100644 includes/normal/UtfNormalUtil.php delete mode 100644 includes/objectcache/APCBagOStuff.php delete mode 100644 includes/objectcache/BagOStuff.php delete mode 100644 includes/objectcache/EmptyBagOStuff.php delete mode 100644 includes/objectcache/HashBagOStuff.php delete mode 100644 includes/objectcache/WinCacheBagOStuff.php delete mode 100644 includes/objectcache/XCacheBagOStuff.php create mode 100644 includes/profiler/ProfileSection.php create mode 100644 includes/profiler/ProfilerFunctions.php delete mode 100644 includes/profiler/ProfilerMwprof.php create mode 100644 includes/profiler/ProfilerSectionOnly.php delete mode 100644 includes/profiler/ProfilerSimpleDB.php delete mode 100644 includes/profiler/ProfilerSimpleText.php delete mode 100644 includes/profiler/ProfilerSimpleTrace.php delete mode 100644 includes/profiler/ProfilerSimpleUDP.php delete mode 100644 includes/profiler/ProfilerStandard.php create mode 100644 includes/profiler/ProfilerXhprof.php create mode 100644 includes/profiler/SectionProfiler.php create mode 100644 includes/profiler/TransactionProfiler.php create mode 100644 includes/profiler/output/ProfilerOutput.php create mode 100644 includes/profiler/output/ProfilerOutputDb.php create mode 100644 includes/profiler/output/ProfilerOutputDump.php create mode 100644 includes/profiler/output/ProfilerOutputStats.php create mode 100644 includes/profiler/output/ProfilerOutputText.php create mode 100644 includes/profiler/output/ProfilerOutputUdp.php create mode 100644 includes/registration/ExtensionProcessor.php create mode 100644 includes/registration/ExtensionRegistry.php create mode 100644 includes/registration/Processor.php delete mode 100644 includes/resourceloader/ResourceLoaderFilePageModule.php create mode 100644 includes/resourceloader/ResourceLoaderImage.php create mode 100644 includes/resourceloader/ResourceLoaderImageModule.php delete mode 100644 includes/resourceloader/ResourceLoaderNoscriptModule.php create mode 100644 includes/resourceloader/ResourceLoaderSkinModule.php create mode 100644 includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php create mode 100644 includes/resourceloader/ResourceLoaderUserDefaultsModule.php create mode 100644 includes/site/CachingSiteStore.php create mode 100644 includes/site/DBSiteStore.php create mode 100644 includes/site/FileBasedSiteLookup.php create mode 100644 includes/site/HashSiteStore.php create mode 100644 includes/site/SiteExporter.php create mode 100644 includes/site/SiteImporter.php create mode 100644 includes/site/SiteLookup.php create mode 100644 includes/site/SitesCacheFileBuilder.php create mode 100644 includes/skins/BaseTemplate.php create mode 100644 includes/skins/MediaWikiI18N.php create mode 100644 includes/skins/QuickTemplate.php create mode 100644 includes/skins/SkinApi.php create mode 100644 includes/skins/SkinApiTemplate.php create mode 100644 includes/specials/SpecialApiHelp.php create mode 100644 includes/specials/SpecialEditTags.php delete mode 100644 includes/specials/SpecialPopularpages.php create mode 100644 includes/templates/NoLocalSettings.mustache delete mode 100644 includes/templates/NoLocalSettings.php create mode 100644 includes/title/ForeignTitle.php create mode 100644 includes/title/ForeignTitleFactory.php create mode 100644 includes/title/ImportTitleFactory.php create mode 100644 includes/title/NaiveForeignTitleFactory.php create mode 100644 includes/title/NaiveImportTitleFactory.php create mode 100644 includes/title/NamespaceAwareForeignTitleFactory.php create mode 100644 includes/title/NamespaceImportTitleFactory.php create mode 100644 includes/title/SubpageImportTitleFactory.php delete mode 100644 includes/utils/ArrayUtils.php create mode 100644 includes/utils/AutoloadGenerator.php delete mode 100644 includes/utils/Cdb.php delete mode 100644 includes/utils/CdbDBA.php delete mode 100644 includes/utils/CdbPHP.php delete mode 100644 includes/utils/StringUtils.php (limited to 'includes') diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index 9bc92be9..b14114d7 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -56,8 +56,6 @@ class AjaxDispatcher { * Load up our object with user supplied data */ function __construct( Config $config ) { - wfProfileIn( __METHOD__ ); - $this->config = $config; $this->mode = ""; @@ -88,13 +86,11 @@ class AjaxDispatcher { } break; default: - wfProfileOut( __METHOD__ ); return; # Or we could throw an exception: # throw new MWException( __METHOD__ . ' called without any data (mode empty).' ); } - wfProfileOut( __METHOD__ ); } /** @@ -110,11 +106,8 @@ class AjaxDispatcher { return; } - wfProfileIn( __METHOD__ ); - if ( !in_array( $this->func_name, $this->config->get( 'AjaxExportList' ) ) ) { wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" ); - wfHttpError( 400, 'Bad Request', @@ -127,7 +120,6 @@ class AjaxDispatcher { 'You are not allowed to view pages.' ); } else { wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" ); - try { $result = call_user_func_array( $this->func_name, $this->args ); @@ -162,6 +154,5 @@ class AjaxDispatcher { } } - wfProfileOut( __METHOD__ ); } } diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 6b0daa14..6344c276 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -25,1199 +25,7 @@ * Extension classes are specified with $wgAutoloadClasses * This array is a global instead of a static member of AutoLoader to work around a bug in APC */ -global $wgAutoloadLocalClasses; - -$wgAutoloadLocalClasses = array( - # Includes - 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', - 'AjaxResponse' => 'includes/AjaxResponse.php', - 'AtomFeed' => 'includes/Feed.php', - 'AuthPlugin' => 'includes/AuthPlugin.php', - 'AuthPluginUser' => 'includes/AuthPlugin.php', - 'Autopromote' => 'includes/Autopromote.php', - 'Block' => 'includes/Block.php', - 'BloomCache' => 'includes/cache/bloom/BloomCache.php', - 'BloomCacheRedis' => 'includes/cache/bloom/BloomCacheRedis.php', - 'BloomFilterTitleHasLogs' => 'includes/cache/bloom/BloomFilters.php', - 'CacheHelper' => 'includes/CacheHelper.php', - 'Category' => 'includes/Category.php', - 'CategoryFinder' => 'includes/CategoryFinder.php', - 'CategoryViewer' => 'includes/CategoryViewer.php', - 'ChangeTags' => 'includes/ChangeTags.php', - 'ChannelFeed' => 'includes/Feed.php', - 'Collation' => 'includes/Collation.php', - 'CollationCkb' => 'includes/Collation.php', - 'CollationEt' => 'includes/Collation.php', - 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', - 'Cookie' => 'includes/Cookie.php', - 'CookieJar' => 'includes/Cookie.php', - 'CurlHttpRequest' => 'includes/HttpFunctions.php', - 'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php', - 'DerivativeRequest' => 'includes/WebRequest.php', - 'DiffHistoryBlob' => 'includes/HistoryBlob.php', - 'DummyLinker' => 'includes/Linker.php', - 'Dump7ZipOutput' => 'includes/Export.php', - 'DumpBZip2Output' => 'includes/Export.php', - 'DumpFileOutput' => 'includes/Export.php', - 'DumpFilter' => 'includes/Export.php', - 'DumpGZipOutput' => 'includes/Export.php', - 'DumpLatestFilter' => 'includes/Export.php', - 'DumpMultiWriter' => 'includes/Export.php', - 'DumpNamespaceFilter' => 'includes/Export.php', - 'DumpNotalkFilter' => 'includes/Export.php', - 'DumpOutput' => 'includes/Export.php', - 'DumpPipeOutput' => 'includes/Export.php', - 'EditPage' => 'includes/EditPage.php', - 'EmptyBloomCache' => 'includes/cache/bloom/BloomCache.php', - 'Fallback' => 'includes/Fallback.php', - 'FauxRequest' => 'includes/WebRequest.php', - 'FauxResponse' => 'includes/WebResponse.php', - 'FeedItem' => 'includes/Feed.php', - 'FeedUtils' => 'includes/FeedUtils.php', - 'FileDeleteForm' => 'includes/FileDeleteForm.php', - 'ForkController' => 'includes/ForkController.php', - 'FormOptions' => 'includes/FormOptions.php', - 'GitInfo' => 'includes/GitInfo.php', - 'HistoryBlob' => 'includes/HistoryBlob.php', - 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', - 'HistoryBlobStub' => 'includes/HistoryBlob.php', - 'Hooks' => 'includes/Hooks.php', - 'Html' => 'includes/Html.php', - 'HtmlFormatter' => 'includes/HtmlFormatter.php', - 'HTMLApiField' => 'includes/htmlform/HTMLApiField.php', - 'HTMLAutoCompleteSelectField' => 'includes/htmlform/HTMLAutoCompleteSelectField.php', - 'HTMLButtonField' => 'includes/htmlform/HTMLButtonField.php', - 'HTMLCheckField' => 'includes/htmlform/HTMLCheckField.php', - 'HTMLCheckMatrix' => 'includes/htmlform/HTMLCheckMatrix.php', - 'HTMLFormFieldCloner' => 'includes/htmlform/HTMLFormFieldCloner.php', - 'HTMLEditTools' => 'includes/htmlform/HTMLEditTools.php', - 'HTMLFloatField' => 'includes/htmlform/HTMLFloatField.php', - 'HTMLForm' => 'includes/htmlform/HTMLForm.php', - 'HTMLFormField' => 'includes/htmlform/HTMLFormField.php', - 'HTMLFormFieldRequiredOptionsException' => - 'includes/htmlform/HTMLFormFieldRequiredOptionsException.php', - 'HTMLHiddenField' => 'includes/htmlform/HTMLHiddenField.php', - 'HTMLInfoField' => 'includes/htmlform/HTMLInfoField.php', - 'HTMLIntField' => 'includes/htmlform/HTMLIntField.php', - 'HTMLNestedFilterable' => 'includes/htmlform/HTMLNestedFilterable.php', - 'HTMLMultiSelectField' => 'includes/htmlform/HTMLMultiSelectField.php', - 'HTMLRadioField' => 'includes/htmlform/HTMLRadioField.php', - 'HTMLSelectAndOtherField' => 'includes/htmlform/HTMLSelectAndOtherField.php', - 'HTMLSelectField' => 'includes/htmlform/HTMLSelectField.php', - 'HTMLSelectLimitField' => 'includes/htmlform/HTMLSelectLimitField.php', - 'HTMLSelectOrOtherField' => 'includes/htmlform/HTMLSelectOrOtherField.php', - 'HTMLSubmitField' => 'includes/htmlform/HTMLSubmitField.php', - 'HTMLTextAreaField' => 'includes/htmlform/HTMLTextAreaField.php', - 'HTMLTextField' => 'includes/htmlform/HTMLTextField.php', - 'Http' => 'includes/HttpFunctions.php', - 'IcuCollation' => 'includes/Collation.php', - 'IdentityCollation' => 'includes/Collation.php', - 'ImportStreamSource' => 'includes/Import.php', - 'ImportStringSource' => 'includes/Import.php', - 'Interwiki' => 'includes/interwiki/Interwiki.php', - 'License' => 'includes/Licenses.php', - 'Licenses' => 'includes/Licenses.php', - 'Linker' => 'includes/Linker.php', - 'LinkFilter' => 'includes/LinkFilter.php', - 'MagicWord' => 'includes/MagicWord.php', - 'MagicWordArray' => 'includes/MagicWord.php', - 'MediaWiki' => 'includes/MediaWiki.php', - 'MediaWikiVersionFetcher' => 'includes/MediaWikiVersionFetcher.php', - 'Message' => 'includes/Message.php', - 'MessageBlobStore' => 'includes/MessageBlobStore.php', - 'MimeMagic' => 'includes/MimeMagic.php', - 'MovePage' => 'includes/MovePage.php', - 'MWHookException' => 'includes/Hooks.php', - 'MWHttpRequest' => 'includes/HttpFunctions.php', - 'MWNamespace' => 'includes/MWNamespace.php', - 'OutputPage' => 'includes/OutputPage.php', - 'PathRouter' => 'includes/PathRouter.php', - 'PathRouterPatternReplacer' => 'includes/PathRouter.php', - 'PhpHttpRequest' => 'includes/HttpFunctions.php', - 'PoolCounter' => 'includes/poolcounter/PoolCounter.php', - 'PoolCounter_Stub' => 'includes/poolcounter/PoolCounter.php', - 'PoolCounterRedis' => 'includes/poolcounter/PoolCounterRedis.php', - 'PoolCounterWork' => 'includes/poolcounter/PoolCounterWork.php', - 'PoolCounterWorkViaCallback' => 'includes/poolcounter/PoolCounterWorkViaCallback.php', - 'PoolWorkArticleView' => 'includes/poolcounter/PoolWorkArticleView.php', - 'Preferences' => 'includes/Preferences.php', - 'PreferencesForm' => 'includes/Preferences.php', - 'PrefixSearch' => 'includes/PrefixSearch.php', - 'ProtectionForm' => 'includes/ProtectionForm.php', - 'RawMessage' => 'includes/Message.php', - 'RevisionItem' => 'includes/RevisionList.php', - 'RevisionItemBase' => 'includes/RevisionList.php', - 'RevisionListBase' => 'includes/RevisionList.php', - 'Revision' => 'includes/Revision.php', - 'RevisionList' => 'includes/RevisionList.php', - 'RSSFeed' => 'includes/Feed.php', - 'Sanitizer' => 'includes/Sanitizer.php', - 'SiteConfiguration' => 'includes/SiteConfiguration.php', - 'SiteStats' => 'includes/SiteStats.php', - 'SiteStatsInit' => 'includes/SiteStats.php', - 'SquidPurgeClient' => 'includes/SquidPurgeClient.php', - 'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php', - 'StatCounter' => 'includes/StatCounter.php', - 'Status' => 'includes/Status.php', - 'StreamFile' => 'includes/StreamFile.php', - 'StringPrefixSearch' => 'includes/PrefixSearch.php', - 'StubObject' => 'includes/StubObject.php', - 'StubUserLang' => 'includes/StubObject.php', - 'MWTimestamp' => 'includes/MWTimestamp.php', - 'TimestampException' => 'includes/TimestampException.php', - 'Title' => 'includes/Title.php', - 'TitleArray' => 'includes/TitleArray.php', - 'TitleArrayFromResult' => 'includes/TitleArrayFromResult.php', - 'TitlePrefixSearch' => 'includes/PrefixSearch.php', - 'UploadSourceAdapter' => 'includes/Import.php', - 'UppercaseCollation' => 'includes/Collation.php', - 'User' => 'includes/User.php', - 'UserArray' => 'includes/UserArray.php', - 'UserArrayFromResult' => 'includes/UserArrayFromResult.php', - 'UserRightsProxy' => 'includes/UserRightsProxy.php', - 'WatchedItem' => 'includes/WatchedItem.php', - 'WebRequest' => 'includes/WebRequest.php', - 'WebRequestUpload' => 'includes/WebRequest.php', - 'WebResponse' => 'includes/WebResponse.php', - 'WikiExporter' => 'includes/Export.php', - 'WikiImporter' => 'includes/Import.php', - 'WikiRevision' => 'includes/Import.php', - 'WikiMap' => 'includes/WikiMap.php', - 'WikiReference' => 'includes/WikiMap.php', - 'Xml' => 'includes/Xml.php', - 'XmlDumpWriter' => 'includes/Export.php', - 'XmlJsCode' => 'includes/Xml.php', - 'XmlSelect' => 'includes/Xml.php', - - # includes/actions - 'Action' => 'includes/actions/Action.php', - 'CachedAction' => 'includes/actions/CachedAction.php', - 'CreditsAction' => 'includes/actions/CreditsAction.php', - 'DeleteAction' => 'includes/actions/DeleteAction.php', - 'EditAction' => 'includes/actions/EditAction.php', - 'FormlessAction' => 'includes/actions/FormlessAction.php', - 'FormAction' => 'includes/actions/FormAction.php', - 'HistoryAction' => 'includes/actions/HistoryAction.php', - 'HistoryPager' => 'includes/actions/HistoryAction.php', - 'InfoAction' => 'includes/actions/InfoAction.php', - 'MarkpatrolledAction' => 'includes/actions/MarkpatrolledAction.php', - 'ProtectAction' => 'includes/actions/ProtectAction.php', - 'PurgeAction' => 'includes/actions/PurgeAction.php', - 'RawAction' => 'includes/actions/RawAction.php', - 'RenderAction' => 'includes/actions/RenderAction.php', - 'RevertAction' => 'includes/actions/RevertAction.php', - 'RevisiondeleteAction' => 'includes/actions/RevisiondeleteAction.php', - 'RollbackAction' => 'includes/actions/RollbackAction.php', - 'SubmitAction' => 'includes/actions/SubmitAction.php', - 'UnprotectAction' => 'includes/actions/UnprotectAction.php', - 'UnwatchAction' => 'includes/actions/UnwatchAction.php', - 'ViewAction' => 'includes/actions/ViewAction.php', - 'WatchAction' => 'includes/actions/WatchAction.php', - - # includes/api - 'ApiBase' => 'includes/api/ApiBase.php', - 'ApiBlock' => 'includes/api/ApiBlock.php', - 'ApiClearHasMsg' => 'includes/api/ApiClearHasMsg.php', - 'ApiComparePages' => 'includes/api/ApiComparePages.php', - 'ApiCreateAccount' => 'includes/api/ApiCreateAccount.php', - 'ApiDelete' => 'includes/api/ApiDelete.php', - 'ApiDisabled' => 'includes/api/ApiDisabled.php', - 'ApiEditPage' => 'includes/api/ApiEditPage.php', - 'ApiEmailUser' => 'includes/api/ApiEmailUser.php', - 'ApiExpandTemplates' => 'includes/api/ApiExpandTemplates.php', - 'ApiFeedContributions' => 'includes/api/ApiFeedContributions.php', - 'ApiFeedRecentChanges' => 'includes/api/ApiFeedRecentChanges.php', - 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php', - 'ApiFileRevert' => 'includes/api/ApiFileRevert.php', - 'ApiFormatBase' => 'includes/api/ApiFormatBase.php', - 'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php', - 'ApiFormatDump' => 'includes/api/ApiFormatDump.php', - 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatFeedWrapper.php', - 'ApiFormatJson' => 'includes/api/ApiFormatJson.php', - 'ApiFormatNone' => 'includes/api/ApiFormatNone.php', - 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php', - 'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php', - 'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php', - 'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php', - 'ApiFormatXml' => 'includes/api/ApiFormatXml.php', - 'ApiFormatXmlRsd' => 'includes/api/ApiRsd.php', - 'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php', - 'ApiHelp' => 'includes/api/ApiHelp.php', - 'ApiImageRotate' => 'includes/api/ApiImageRotate.php', - 'ApiImport' => 'includes/api/ApiImport.php', - 'ApiImportReporter' => 'includes/api/ApiImport.php', - 'ApiLogin' => 'includes/api/ApiLogin.php', - 'ApiLogout' => 'includes/api/ApiLogout.php', - 'ApiMain' => 'includes/api/ApiMain.php', - 'ApiModuleManager' => 'includes/api/ApiModuleManager.php', - 'ApiMove' => 'includes/api/ApiMove.php', - 'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php', - 'ApiOptions' => 'includes/api/ApiOptions.php', - 'ApiPageSet' => 'includes/api/ApiPageSet.php', - 'ApiParamInfo' => 'includes/api/ApiParamInfo.php', - 'ApiParse' => 'includes/api/ApiParse.php', - 'ApiPatrol' => 'includes/api/ApiPatrol.php', - 'ApiProtect' => 'includes/api/ApiProtect.php', - 'ApiPurge' => 'includes/api/ApiPurge.php', - 'ApiQuery' => 'includes/api/ApiQuery.php', - 'ApiQueryAllCategories' => 'includes/api/ApiQueryAllCategories.php', - 'ApiQueryAllImages' => 'includes/api/ApiQueryAllImages.php', - 'ApiQueryAllLinks' => 'includes/api/ApiQueryAllLinks.php', - 'ApiQueryAllMessages' => 'includes/api/ApiQueryAllMessages.php', - 'ApiQueryAllPages' => 'includes/api/ApiQueryAllPages.php', - 'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php', - 'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php', - 'ApiQueryBacklinksprop' => 'includes/api/ApiQueryBacklinksprop.php', - 'ApiQueryBase' => 'includes/api/ApiQueryBase.php', - 'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php', - 'ApiQueryCategories' => 'includes/api/ApiQueryCategories.php', - 'ApiQueryCategoryInfo' => 'includes/api/ApiQueryCategoryInfo.php', - 'ApiQueryCategoryMembers' => 'includes/api/ApiQueryCategoryMembers.php', - 'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php', - 'ApiQueryContributors' => 'includes/api/ApiQueryContributors.php', - 'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php', - 'ApiQueryDisabled' => 'includes/api/ApiQueryDisabled.php', - 'ApiQueryDuplicateFiles' => 'includes/api/ApiQueryDuplicateFiles.php', - 'ApiQueryExternalLinks' => 'includes/api/ApiQueryExternalLinks.php', - 'ApiQueryExtLinksUsage' => 'includes/api/ApiQueryExtLinksUsage.php', - 'ApiQueryFilearchive' => 'includes/api/ApiQueryFilearchive.php', - 'ApiQueryGeneratorBase' => 'includes/api/ApiQueryBase.php', - 'ApiQueryImageInfo' => 'includes/api/ApiQueryImageInfo.php', - 'ApiQueryImages' => 'includes/api/ApiQueryImages.php', - 'ApiQueryInfo' => 'includes/api/ApiQueryInfo.php', - 'ApiQueryIWBacklinks' => 'includes/api/ApiQueryIWBacklinks.php', - 'ApiQueryIWLinks' => 'includes/api/ApiQueryIWLinks.php', - 'ApiQueryLangBacklinks' => 'includes/api/ApiQueryLangBacklinks.php', - 'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php', - 'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php', - 'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php', - 'ApiQueryORM' => 'includes/api/ApiQueryORM.php', - 'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php', - 'ApiQueryPagesWithProp' => 'includes/api/ApiQueryPagesWithProp.php', - 'ApiQueryPagePropNames' => 'includes/api/ApiQueryPagePropNames.php', - 'ApiQueryPrefixSearch' => 'includes/api/ApiQueryPrefixSearch.php', - 'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php', - 'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php', - 'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php', - 'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php', - 'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php', - 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php', - 'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php', - 'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php', - 'ApiQueryStashImageInfo' => 'includes/api/ApiQueryStashImageInfo.php', - 'ApiQueryTags' => 'includes/api/ApiQueryTags.php', - 'ApiQueryTokens' => 'includes/api/ApiQueryTokens.php', - 'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php', - 'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php', - 'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php', - 'ApiQueryWatchlistRaw' => 'includes/api/ApiQueryWatchlistRaw.php', - 'ApiResult' => 'includes/api/ApiResult.php', - 'ApiRevisionDelete' => 'includes/api/ApiRevisionDelete.php', - 'ApiRollback' => 'includes/api/ApiRollback.php', - 'ApiRsd' => 'includes/api/ApiRsd.php', - 'ApiSetNotificationTimestamp' => 'includes/api/ApiSetNotificationTimestamp.php', - 'ApiTokens' => 'includes/api/ApiTokens.php', - 'ApiUnblock' => 'includes/api/ApiUnblock.php', - 'ApiUndelete' => 'includes/api/ApiUndelete.php', - 'ApiUpload' => 'includes/api/ApiUpload.php', - 'ApiUserrights' => 'includes/api/ApiUserrights.php', - 'ApiWatch' => 'includes/api/ApiWatch.php', - 'UsageException' => 'includes/api/ApiMain.php', - - # includes/cache - 'BacklinkCache' => 'includes/cache/BacklinkCache.php', - 'CacheDependency' => 'includes/cache/CacheDependency.php', - 'CacheHelper' => 'includes/cache/CacheHelper.php', - 'ConstantDependency' => 'includes/cache/CacheDependency.php', - 'DependencyWrapper' => 'includes/cache/CacheDependency.php', - 'FileCacheBase' => 'includes/cache/FileCacheBase.php', - 'FileDependency' => 'includes/cache/CacheDependency.php', - 'GenderCache' => 'includes/cache/GenderCache.php', - 'GlobalDependency' => 'includes/cache/CacheDependency.php', - 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php', - 'ICacheHelper' => 'includes/cache/CacheHelper.php', - 'LCStore' => 'includes/cache/LocalisationCache.php', - 'LCStoreCDB' => 'includes/cache/LocalisationCache.php', - 'LCStoreDB' => 'includes/cache/LocalisationCache.php', - 'LCStoreNull' => 'includes/cache/LocalisationCache.php', - 'LinkBatch' => 'includes/cache/LinkBatch.php', - 'LinkCache' => 'includes/cache/LinkCache.php', - 'LocalisationCache' => 'includes/cache/LocalisationCache.php', - 'LocalisationCacheBulkLoad' => 'includes/cache/LocalisationCache.php', - 'MapCacheLRU' => 'includes/cache/MapCacheLRU.php', - 'MessageCache' => 'includes/cache/MessageCache.php', - 'ObjectFileCache' => 'includes/cache/ObjectFileCache.php', - 'ResourceFileCache' => 'includes/cache/ResourceFileCache.php', - 'UserCache' => 'includes/cache/UserCache.php', - - # includes/changes - 'ChangesFeed' => 'includes/changes/ChangesFeed.php', - 'ChangesList' => 'includes/changes/ChangesList.php', - 'EnhancedChangesList' => 'includes/changes/EnhancedChangesList.php', - 'OldChangesList' => 'includes/changes/OldChangesList.php', - 'RCCacheEntry' => 'includes/changes/RCCacheEntry.php', - 'RCCacheEntryFactory' => 'includes/changes/RCCacheEntryFactory.php', - 'RecentChange' => 'includes/changes/RecentChange.php', - - # includes/clientpool - 'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php', - 'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php', - - # includes/composer - 'ComposerPackageModifier' => 'includes/composer/ComposerPackageModifier.php', - 'ComposerVersionNormalizer' => 'includes/composer/ComposerVersionNormalizer.php', - - # includes/config - 'Config' => 'includes/config/Config.php', - 'ConfigException' => 'includes/config/ConfigException.php', - 'ConfigFactory' => 'includes/config/ConfigFactory.php', - 'GlobalVarConfig' => 'includes/config/GlobalVarConfig.php', - 'HashConfig' => 'includes/config/HashConfig.php', - 'MultiConfig' => 'includes/config/MultiConfig.php', - 'MutableConfig' => 'includes/config/MutableConfig.php', - - # includes/content - 'AbstractContent' => 'includes/content/AbstractContent.php', - 'CodeContentHandler' => 'includes/content/CodeContentHandler.php', - 'Content' => 'includes/content/Content.php', - 'ContentHandler' => 'includes/content/ContentHandler.php', - 'CssContent' => 'includes/content/CssContent.php', - 'CssContentHandler' => 'includes/content/CssContentHandler.php', - 'JavaScriptContent' => 'includes/content/JavaScriptContent.php', - 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php', - 'JsonContent' => 'includes/content/JsonContent.php', - 'JsonContentHandler' => 'includes/content/JsonContentHandler.php', - 'MessageContent' => 'includes/content/MessageContent.php', - 'MWContentSerializationException' => 'includes/content/ContentHandler.php', - 'TextContent' => 'includes/content/TextContent.php', - 'TextContentHandler' => 'includes/content/TextContentHandler.php', - 'WikitextContent' => 'includes/content/WikitextContent.php', - 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', - - # includes/context - 'ContextSource' => 'includes/context/ContextSource.php', - 'DerivativeContext' => 'includes/context/DerivativeContext.php', - 'IContextSource' => 'includes/context/IContextSource.php', - 'RequestContext' => 'includes/context/RequestContext.php', - - # includes/dao - 'IDBAccessObject' => 'includes/dao/IDBAccessObject.php', - 'DBAccessBase' => 'includes/dao/DBAccessBase.php', - - # includes/db - 'Blob' => 'includes/db/DatabaseUtility.php', - 'ChronologyProtector' => 'includes/db/ChronologyProtector.php', - 'CloneDatabase' => 'includes/db/CloneDatabase.php', - 'DatabaseBase' => 'includes/db/Database.php', - 'DatabaseMssql' => 'includes/db/DatabaseMssql.php', - 'DatabaseMysql' => 'includes/db/DatabaseMysql.php', - 'DatabaseMysqlBase' => 'includes/db/DatabaseMysqlBase.php', - 'DatabaseMysqli' => 'includes/db/DatabaseMysqli.php', - 'DatabaseOracle' => 'includes/db/DatabaseOracle.php', - 'DatabasePostgres' => 'includes/db/DatabasePostgres.php', - 'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php', - 'DatabaseSqliteStandalone' => 'includes/db/DatabaseSqlite.php', - 'DatabaseType' => 'includes/db/Database.php', - 'DBAccessError' => 'includes/db/LBFactory.php', - 'DBConnectionError' => 'includes/db/DatabaseError.php', - 'DBConnRef' => 'includes/db/LoadBalancer.php', - 'DBError' => 'includes/db/DatabaseError.php', - 'DBExpectedError' => 'includes/db/DatabaseError.php', - 'DBObject' => 'includes/db/DatabaseUtility.php', - 'IDatabase' => 'includes/db/Database.php', - 'IORMRow' => 'includes/db/IORMRow.php', - 'IORMTable' => 'includes/db/IORMTable.php', - 'DBMasterPos' => 'includes/db/DatabaseUtility.php', - 'DBQueryError' => 'includes/db/DatabaseError.php', - 'DBUnexpectedError' => 'includes/db/DatabaseError.php', - 'FakeResultWrapper' => 'includes/db/DatabaseUtility.php', - 'Field' => 'includes/db/DatabaseUtility.php', - 'LBFactory' => 'includes/db/LBFactory.php', - 'LBFactoryFake' => 'includes/db/LBFactory.php', - 'LBFactoryMulti' => 'includes/db/LBFactoryMulti.php', - 'LBFactorySimple' => 'includes/db/LBFactory.php', - 'LBFactorySingle' => 'includes/db/LBFactorySingle.php', - 'LikeMatch' => 'includes/db/DatabaseUtility.php', - 'LoadBalancer' => 'includes/db/LoadBalancer.php', - 'LoadBalancerSingle' => 'includes/db/LBFactorySingle.php', - 'LoadMonitor' => 'includes/db/LoadMonitor.php', - 'LoadMonitorMySQL' => 'includes/db/LoadMonitor.php', - 'LoadMonitorNull' => 'includes/db/LoadMonitor.php', - 'MssqlField' => 'includes/db/DatabaseMssql.php', - 'MssqlBlob' => 'includes/db/DatabaseMssql.php', - 'MssqlResultWrapper' => 'includes/db/DatabaseMssql.php', - 'MySQLField' => 'includes/db/DatabaseMysqlBase.php', - 'MySQLMasterPos' => 'includes/db/DatabaseMysqlBase.php', - 'ORAField' => 'includes/db/DatabaseOracle.php', - 'ORAResult' => 'includes/db/DatabaseOracle.php', - 'ORMIterator' => 'includes/db/ORMIterator.php', - 'ORMResult' => 'includes/db/ORMResult.php', - 'ORMRow' => 'includes/db/ORMRow.php', - 'ORMTable' => 'includes/db/ORMTable.php', - 'PostgresField' => 'includes/db/DatabasePostgres.php', - 'PostgresTransactionState' => 'includes/db/DatabasePostgres.php', - 'ResultWrapper' => 'includes/db/DatabaseUtility.php', - 'SavepointPostgres' => 'includes/db/DatabasePostgres.php', - 'SQLiteField' => 'includes/db/DatabaseSqlite.php', - - # includes/debug - 'MWDebug' => 'includes/debug/MWDebug.php', - - # includes/deferred - 'DataUpdate' => 'includes/deferred/DataUpdate.php', - 'DeferrableUpdate' => 'includes/deferred/DeferredUpdates.php', - 'DeferredUpdates' => 'includes/deferred/DeferredUpdates.php', - 'HTMLCacheUpdate' => 'includes/deferred/HTMLCacheUpdate.php', - 'LinksDeletionUpdate' => 'includes/deferred/LinksUpdate.php', - 'LinksUpdate' => 'includes/deferred/LinksUpdate.php', - 'MWCallableUpdate' => 'includes/deferred/CallableUpdate.php', - 'SearchUpdate' => 'includes/deferred/SearchUpdate.php', - 'SiteStatsUpdate' => 'includes/deferred/SiteStatsUpdate.php', - 'SqlDataUpdate' => 'includes/deferred/SqlDataUpdate.php', - 'SquidUpdate' => 'includes/deferred/SquidUpdate.php', - 'ViewCountUpdate' => 'includes/deferred/ViewCountUpdate.php', - - # includes/diff - 'DiffEngine' => 'includes/diff/DairikiDiff.php', - 'DiffOp' => 'includes/diff/DairikiDiff.php', - 'DiffOpAdd' => 'includes/diff/DairikiDiff.php', - 'DiffOpChange' => 'includes/diff/DairikiDiff.php', - 'DiffOpCopy' => 'includes/diff/DairikiDiff.php', - 'DiffOpDelete' => 'includes/diff/DairikiDiff.php', - 'HWLDFWordAccumulator' => 'includes/diff/DairikiDiff.php', - 'ArrayDiffFormatter' => 'includes/diff/ArrayDiffFormatter.php', - 'Diff' => 'includes/diff/DairikiDiff.php', - 'DifferenceEngine' => 'includes/diff/DifferenceEngine.php', - 'DiffFormatter' => 'includes/diff/DiffFormatter.php', - 'MappedDiff' => 'includes/diff/DairikiDiff.php', - 'RangeDifference' => 'includes/diff/WikiDiff3.php', - 'TableDiffFormatter' => 'includes/diff/TableDiffFormatter.php', - 'UnifiedDiffFormatter' => 'includes/diff/UnifiedDiffFormatter.php', - 'WikiDiff3' => 'includes/diff/WikiDiff3.php', - 'WordLevelDiff' => 'includes/diff/DairikiDiff.php', - - # includes/exception - 'UserBlockedError' => 'includes/exception/UserBlockedError.php', - 'UserNotLoggedIn' => 'includes/exception/UserNotLoggedIn.php', - 'ThrottledError' => 'includes/exception/ThrottledError.php', - 'ReadOnlyError' => 'includes/exception/ReadOnlyError.php', - 'PermissionsError' => 'includes/exception/PermissionsError.php', - 'MWException' => 'includes/exception/MWException.php', - 'MWExceptionHandler' => 'includes/exception/MWExceptionHandler.php', - 'HttpError' => 'includes/exception/HttpError.php', - 'BadTitleError' => 'includes/exception/BadTitleError.php', - 'ErrorPageError' => 'includes/exception/ErrorPageError.php', - 'FatalError' => 'includes/exception/FatalError.php', - - # includes/externalstore - 'ExternalStore' => 'includes/externalstore/ExternalStore.php', - 'ExternalStoreDB' => 'includes/externalstore/ExternalStoreDB.php', - 'ExternalStoreHttp' => 'includes/externalstore/ExternalStoreHttp.php', - 'ExternalStoreMedium' => 'includes/externalstore/ExternalStoreMedium.php', - 'ExternalStoreMwstore' => 'includes/externalstore/ExternalStoreMwstore.php', - - # includes/filebackend - 'FileBackendGroup' => 'includes/filebackend/FileBackendGroup.php', - 'FileBackend' => 'includes/filebackend/FileBackend.php', - 'FileBackendError' => 'includes/filebackend/FileBackend.php', - 'FileBackendException' => 'includes/filebackend/FileBackend.php', - 'FileBackendStore' => 'includes/filebackend/FileBackendStore.php', - 'FileBackendStoreShardListIterator' => 'includes/filebackend/FileBackendStore.php', - 'FileBackendStoreShardDirIterator' => 'includes/filebackend/FileBackendStore.php', - 'FileBackendStoreShardFileIterator' => 'includes/filebackend/FileBackendStore.php', - 'FileBackendMultiWrite' => 'includes/filebackend/FileBackendMultiWrite.php', - 'FileBackendStoreOpHandle' => 'includes/filebackend/FileBackendStore.php', - 'FSFile' => 'includes/filebackend/FSFile.php', - 'FSFileBackend' => 'includes/filebackend/FSFileBackend.php', - 'FSFileBackendList' => 'includes/filebackend/FSFileBackend.php', - 'FSFileBackendDirList' => 'includes/filebackend/FSFileBackend.php', - 'FSFileBackendFileList' => 'includes/filebackend/FSFileBackend.php', - 'FSFileOpHandle' => 'includes/filebackend/FSFileBackend.php', - 'MemoryFileBackend' => 'includes/filebackend/MemoryFileBackend.php', - 'SwiftFileBackend' => 'includes/filebackend/SwiftFileBackend.php', - 'SwiftFileBackendList' => 'includes/filebackend/SwiftFileBackend.php', - 'SwiftFileBackendDirList' => 'includes/filebackend/SwiftFileBackend.php', - 'SwiftFileBackendFileList' => 'includes/filebackend/SwiftFileBackend.php', - 'SwiftFileOpHandle' => 'includes/filebackend/SwiftFileBackend.php', - 'TempFSFile' => 'includes/filebackend/TempFSFile.php', - 'FileJournal' => 'includes/filebackend/filejournal/FileJournal.php', - 'DBFileJournal' => 'includes/filebackend/filejournal/DBFileJournal.php', - 'NullFileJournal' => 'includes/filebackend/filejournal/FileJournal.php', - 'LockManagerGroup' => 'includes/filebackend/lockmanager/LockManagerGroup.php', - 'LockManager' => 'includes/filebackend/lockmanager/LockManager.php', - 'ScopedLock' => 'includes/filebackend/lockmanager/ScopedLock.php', - 'FSLockManager' => 'includes/filebackend/lockmanager/FSLockManager.php', - 'DBLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', - 'MemcLockManager' => 'includes/filebackend/lockmanager/MemcLockManager.php', - 'QuorumLockManager' => 'includes/filebackend/lockmanager/QuorumLockManager.php', - 'MySqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', - 'PostgreSqlLockManager' => 'includes/filebackend/lockmanager/DBLockManager.php', - 'RedisLockManager' => 'includes/filebackend/lockmanager/RedisLockManager.php', - 'NullLockManager' => 'includes/filebackend/lockmanager/LockManager.php', - 'FileOp' => 'includes/filebackend/FileOp.php', - 'FileOpBatch' => 'includes/filebackend/FileOpBatch.php', - 'StoreFileOp' => 'includes/filebackend/FileOp.php', - 'CopyFileOp' => 'includes/filebackend/FileOp.php', - 'MoveFileOp' => 'includes/filebackend/FileOp.php', - 'DeleteFileOp' => 'includes/filebackend/FileOp.php', - 'CreateFileOp' => 'includes/filebackend/FileOp.php', - 'DescribeFileOp' => 'includes/filebackend/FileOp.php', - 'NullFileOp' => 'includes/filebackend/FileOp.php', - - # includes/filerepo - 'FileRepo' => 'includes/filerepo/FileRepo.php', - 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php', - 'ForeignAPIRepo' => 'includes/filerepo/ForeignAPIRepo.php', - 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php', - 'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php', - 'FSRepo' => 'includes/filerepo/FSRepo.php', - 'LocalRepo' => 'includes/filerepo/LocalRepo.php', - 'NullRepo' => 'includes/filerepo/NullRepo.php', - 'RepoGroup' => 'includes/filerepo/RepoGroup.php', - 'TempFileRepo' => 'includes/filerepo/FileRepo.php', - - # includes/filerepo/file - 'ArchivedFile' => 'includes/filerepo/file/ArchivedFile.php', - 'File' => 'includes/filerepo/file/File.php', - 'ForeignAPIFile' => 'includes/filerepo/file/ForeignAPIFile.php', - 'ForeignDBFile' => 'includes/filerepo/file/ForeignDBFile.php', - 'LocalFile' => 'includes/filerepo/file/LocalFile.php', - 'LocalFileDeleteBatch' => 'includes/filerepo/file/LocalFile.php', - 'LocalFileMoveBatch' => 'includes/filerepo/file/LocalFile.php', - 'LocalFileRestoreBatch' => 'includes/filerepo/file/LocalFile.php', - 'OldLocalFile' => 'includes/filerepo/file/OldLocalFile.php', - 'UnregisteredLocalFile' => 'includes/filerepo/file/UnregisteredLocalFile.php', - - # includes/installer - 'CliInstaller' => 'includes/installer/CliInstaller.php', - 'DatabaseInstaller' => 'includes/installer/DatabaseInstaller.php', - 'DatabaseUpdater' => 'includes/installer/DatabaseUpdater.php', - 'InstallDocFormatter' => 'includes/installer/InstallDocFormatter.php', - 'Installer' => 'includes/installer/Installer.php', - 'LocalSettingsGenerator' => 'includes/installer/LocalSettingsGenerator.php', - 'MssqlInstaller' => 'includes/installer/MssqlInstaller.php', - 'MssqlUpdater' => 'includes/installer/MssqlUpdater.php', - 'MysqlInstaller' => 'includes/installer/MysqlInstaller.php', - 'MysqlUpdater' => 'includes/installer/MysqlUpdater.php', - 'OracleInstaller' => 'includes/installer/OracleInstaller.php', - 'OracleUpdater' => 'includes/installer/OracleUpdater.php', - 'PhpXmlBugTester' => 'includes/installer/PhpBugTests.php', - 'PostgresInstaller' => 'includes/installer/PostgresInstaller.php', - 'PostgresUpdater' => 'includes/installer/PostgresUpdater.php', - 'SqliteInstaller' => 'includes/installer/SqliteInstaller.php', - 'SqliteUpdater' => 'includes/installer/SqliteUpdater.php', - 'WebInstaller' => 'includes/installer/WebInstaller.php', - 'WebInstallerComplete' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerCopying' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerDBConnect' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerDBSettings' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerDocument' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerExistingWiki' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerInstall' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerLanguage' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerName' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerOptions' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerReadme' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerReleaseNotes' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerRestart' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerUpgrade' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerUpgradeDoc' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerWelcome' => 'includes/installer/WebInstallerPage.php', - 'WebInstallerOutput' => 'includes/installer/WebInstallerOutput.php', - 'WebInstallerPage' => 'includes/installer/WebInstallerPage.php', - - # includes/job - 'IJobSpecification' => 'includes/jobqueue/JobSpecification.php', - 'Job' => 'includes/jobqueue/Job.php', - 'JobQueue' => 'includes/jobqueue/JobQueue.php', - 'JobQueueAggregator' => 'includes/jobqueue/aggregator/JobQueueAggregator.php', - 'JobQueueAggregatorMemc' => 'includes/jobqueue/aggregator/JobQueueAggregatorMemc.php', - 'JobQueueAggregatorRedis' => 'includes/jobqueue/aggregator/JobQueueAggregatorRedis.php', - 'JobQueueDB' => 'includes/jobqueue/JobQueueDB.php', - 'JobQueueConnectionError' => 'includes/jobqueue/JobQueue.php', - 'JobQueueError' => 'includes/jobqueue/JobQueue.php', - 'JobQueueGroup' => 'includes/jobqueue/JobQueueGroup.php', - 'JobQueueFederated' => 'includes/jobqueue/JobQueueFederated.php', - 'JobQueueRedis' => 'includes/jobqueue/JobQueueRedis.php', - 'JobRunner' => 'includes/jobqueue/JobRunner.php', - 'JobSpecification' => 'includes/jobqueue/JobSpecification.php', - - # includes/jobqueue/jobs - 'DoubleRedirectJob' => 'includes/jobqueue/jobs/DoubleRedirectJob.php', - 'DuplicateJob' => 'includes/jobqueue/jobs/DuplicateJob.php', - 'EmaillingJob' => 'includes/jobqueue/jobs/EmaillingJob.php', - 'EnotifNotifyJob' => 'includes/jobqueue/jobs/EnotifNotifyJob.php', - 'HTMLCacheUpdateJob' => 'includes/jobqueue/jobs/HTMLCacheUpdateJob.php', - 'NullJob' => 'includes/jobqueue/jobs/NullJob.php', - 'RefreshLinksJob' => 'includes/jobqueue/jobs/RefreshLinksJob.php', - 'RefreshLinksJob2' => 'includes/jobqueue/jobs/RefreshLinksJob2.php', - 'UploadFromUrlJob' => 'includes/jobqueue/jobs/UploadFromUrlJob.php', - 'AssembleUploadChunksJob' => 'includes/jobqueue/jobs/AssembleUploadChunksJob.php', - 'PublishStashedFileJob' => 'includes/jobqueue/jobs/PublishStashedFileJob.php', - - # includes/jobqueue/utils - 'BacklinkJobUtils' => 'includes/jobqueue/utils/BacklinkJobUtils.php', - - # includes/json - 'FormatJson' => 'includes/json/FormatJson.php', - - # includes/libs - 'CSSJanus' => 'includes/libs/CSSJanus.php', - 'CSSJanusTokenizer' => 'includes/libs/CSSJanus.php', - 'CSSMin' => 'includes/libs/CSSMin.php', - 'GenericArrayObject' => 'includes/libs/GenericArrayObject.php', - 'HashRing' => 'includes/libs/HashRing.php', - 'HttpStatus' => 'includes/libs/HttpStatus.php', - 'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php', - 'IEUrlExtension' => 'includes/libs/IEUrlExtension.php', - 'MappedIterator' => 'includes/libs/MappedIterator.php', - 'IPSet' => 'includes/libs/IPSet.php', - 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php', - 'JSCompilerContext' => 'includes/libs/jsminplus.php', - 'JSMinPlus' => 'includes/libs/jsminplus.php', - 'JSNode' => 'includes/libs/jsminplus.php', - 'JSParser' => 'includes/libs/jsminplus.php', - 'JSToken' => 'includes/libs/jsminplus.php', - 'JSTokenizer' => 'includes/libs/jsminplus.php', - 'MultiHttpClient' => 'includes/libs/MultiHttpClient.php', - 'MWMessagePack' => 'includes/libs/MWMessagePack.php', - 'ProcessCacheLRU' => 'includes/libs/ProcessCacheLRU.php', - 'RunningStat' => 'includes/libs/RunningStat.php', - 'ScopedCallback' => 'includes/libs/ScopedCallback.php', - 'ScopedPHPTimeout' => 'includes/libs/ScopedPHPTimeout.php', - 'SwiftVirtualRESTService' => 'includes/libs/virtualrest/SwiftVirtualRESTService.php', - 'VirtualRESTService' => 'includes/libs/virtualrest/VirtualRESTService.php', - 'VirtualRESTServiceClient' => 'includes/libs/virtualrest/VirtualRESTServiceClient.php', - 'XmlTypeCheck' => 'includes/libs/XmlTypeCheck.php', - - # includes/libs/lessphp - 'lessc' => 'includes/libs/lessc.inc.php', - 'lessc_parser' => 'includes/libs/lessc.inc.php', - 'lessc_formatter_classic' => 'includes/libs/lessc.inc.php', - 'lessc_formatter_compressed' => 'includes/libs/lessc.inc.php', - 'lessc_formatter_lessjs' => 'includes/libs/lessc.inc.php', - - # includes/logging - 'DatabaseLogEntry' => 'includes/logging/LogEntry.php', - 'DeleteLogFormatter' => 'includes/logging/DeleteLogFormatter.php', - 'LegacyLogFormatter' => 'includes/logging/LogFormatter.php', - 'LogEntry' => 'includes/logging/LogEntry.php', - 'LogEventsList' => 'includes/logging/LogEventsList.php', - 'LogEntryBase' => 'includes/logging/LogEntry.php', - 'LogFormatter' => 'includes/logging/LogFormatter.php', - 'LogPage' => 'includes/logging/LogPage.php', - 'LogPager' => 'includes/logging/LogPager.php', - 'ManualLogEntry' => 'includes/logging/LogEntry.php', - 'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php', - 'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php', - 'PageLangLogFormatter' => 'includes/logging/PageLangLogFormatter.php', - 'PatrolLog' => 'includes/logging/PatrolLog.php', - 'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php', - 'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php', - 'RightsLogFormatter' => 'includes/logging/RightsLogFormatter.php', - - # Image gallery - - 'ImageGallery' => 'includes/gallery/TraditionalImageGallery.php', - 'ImageGalleryBase' => 'includes/gallery/ImageGalleryBase.php', - 'NolinesImageGallery' => 'includes/gallery/NolinesImageGallery.php', - 'TraditionalImageGallery' => 'includes/gallery/TraditionalImageGallery.php', - 'PackedImageGallery' => 'includes/gallery/PackedImageGallery.php', - 'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', - 'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', - - # includes/mail - 'EmailNotification' => 'includes/mail/EmailNotification.php', - 'MailAddress' => 'includes/mail/MailAddress.php', - 'UserMailer' => 'includes/mail/UserMailer.php', - - # includes/media - 'BitmapHandler' => 'includes/media/Bitmap.php', - 'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php', - 'BitmapMetadataHandler' => 'includes/media/BitmapMetadataHandler.php', - 'BmpHandler' => 'includes/media/BMP.php', - 'DjVuHandler' => 'includes/media/DjVu.php', - 'DjVuImage' => 'includes/media/DjVuImage.php', - 'Exif' => 'includes/media/Exif.php', - 'ExifBitmapHandler' => 'includes/media/ExifBitmap.php', - 'FormatMetadata' => 'includes/media/FormatMetadata.php', - 'GIFHandler' => 'includes/media/GIF.php', - 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php', - 'ImageHandler' => 'includes/media/ImageHandler.php', - 'IPTC' => 'includes/media/IPTC.php', - 'JpegHandler' => 'includes/media/Jpeg.php', - 'JpegMetadataExtractor' => 'includes/media/JpegMetadataExtractor.php', - 'MediaHandler' => 'includes/media/MediaHandler.php', - 'MediaTransformError' => 'includes/media/MediaTransformOutput.php', - 'MediaTransformOutput' => 'includes/media/MediaTransformOutput.php', - 'PNGHandler' => 'includes/media/PNG.php', - 'PNGMetadataExtractor' => 'includes/media/PNGMetadataExtractor.php', - 'SvgHandler' => 'includes/media/SVG.php', - 'SVGMetadataExtractor' => 'includes/media/SVGMetadataExtractor.php', - 'SVGReader' => 'includes/media/SVGMetadataExtractor.php', - 'ThumbnailImage' => 'includes/media/MediaTransformOutput.php', - 'TiffHandler' => 'includes/media/Tiff.php', - 'TransformationalImageHandler' => 'includes/media/TransformationalImageHandler.php', - 'TransformParameterError' => 'includes/media/MediaTransformOutput.php', - 'XCFHandler' => 'includes/media/XCF.php', - 'XMPInfo' => 'includes/media/XMPInfo.php', - 'XMPReader' => 'includes/media/XMP.php', - 'XMPValidate' => 'includes/media/XMPValidate.php', - - # includes/normal - 'UtfNormal' => 'includes/normal/UtfNormal.php', - - # includes/objectcache - 'APCBagOStuff' => 'includes/objectcache/APCBagOStuff.php', - 'BagOStuff' => 'includes/objectcache/BagOStuff.php', - 'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php', - 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php', - 'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php', - 'MemCachedClientforWiki' => 'includes/objectcache/MemcachedClient.php', - 'MemcachedBagOStuff' => 'includes/objectcache/MemcachedBagOStuff.php', - 'MemcachedPeclBagOStuff' => 'includes/objectcache/MemcachedPeclBagOStuff.php', - 'MemcachedPhpBagOStuff' => 'includes/objectcache/MemcachedPhpBagOStuff.php', - 'MultiWriteBagOStuff' => 'includes/objectcache/MultiWriteBagOStuff.php', - 'MWMemcached' => 'includes/objectcache/MemcachedClient.php', - 'ObjectCache' => 'includes/objectcache/ObjectCache.php', - 'ObjectCacheSessionHandler' => 'includes/objectcache/ObjectCacheSessionHandler.php', - 'RedisBagOStuff' => 'includes/objectcache/RedisBagOStuff.php', - 'SqlBagOStuff' => 'includes/objectcache/SqlBagOStuff.php', - 'WinCacheBagOStuff' => 'includes/objectcache/WinCacheBagOStuff.php', - 'XCacheBagOStuff' => 'includes/objectcache/XCacheBagOStuff.php', - - # includes/page - 'Article' => 'includes/page/Article.php', - 'CategoryPage' => 'includes/page/CategoryPage.php', - 'ImageHistoryList' => 'includes/page/ImagePage.php', - 'ImageHistoryPseudoPager' => 'includes/page/ImagePage.php', - 'ImagePage' => 'includes/page/ImagePage.php', - 'Page' => 'includes/page/WikiPage.php', - 'WikiCategoryPage' => 'includes/page/WikiCategoryPage.php', - 'WikiFilePage' => 'includes/page/WikiFilePage.php', - 'WikiPage' => 'includes/page/WikiPage.php', - - # includes/pager - 'AlphabeticPager' => 'includes/pager/AlphabeticPager.php', - 'IndexPager' => 'includes/pager/IndexPager.php', - 'Pager' => 'includes/pager/Pager.php', - 'ReverseChronologicalPager' => 'includes/pager/ReverseChronologicalPager.php', - 'TablePager' => 'includes/pager/TablePager.php', - - # includes/parser - 'CacheTime' => 'includes/parser/CacheTime.php', - 'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php', - 'CoreTagHooks' => 'includes/parser/CoreTagHooks.php', - 'DateFormatter' => 'includes/parser/DateFormatter.php', - 'LinkHolderArray' => 'includes/parser/LinkHolderArray.php', - 'MWTidy' => 'includes/parser/MWTidy.php', - 'MWTidyWrapper' => 'includes/parser/MWTidy.php', - 'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', - 'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'PPDPart' => 'includes/parser/Preprocessor_DOM.php', - 'PPDPart_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'PPDStack' => 'includes/parser/Preprocessor_DOM.php', - 'PPDStackElement' => 'includes/parser/Preprocessor_DOM.php', - 'PPDStackElement_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'PPFrame' => 'includes/parser/Preprocessor.php', - 'PPFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', - 'PPFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'PPNode' => 'includes/parser/Preprocessor.php', - 'PPNode_DOM' => 'includes/parser/Preprocessor_DOM.php', - 'PPNode_Hash_Array' => 'includes/parser/Preprocessor_Hash.php', - 'PPNode_Hash_Attr' => 'includes/parser/Preprocessor_Hash.php', - 'PPNode_Hash_Text' => 'includes/parser/Preprocessor_Hash.php', - 'PPNode_Hash_Tree' => 'includes/parser/Preprocessor_Hash.php', - 'PPTemplateFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', - 'PPTemplateFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'Parser' => 'includes/parser/Parser.php', - 'ParserCache' => 'includes/parser/ParserCache.php', - 'ParserOptions' => 'includes/parser/ParserOptions.php', - 'ParserOutput' => 'includes/parser/ParserOutput.php', - 'ParserDiffTest' => 'includes/parser/ParserDiffTest.php', - 'Preprocessor' => 'includes/parser/Preprocessor.php', - 'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php', - 'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php', - 'StripState' => 'includes/parser/StripState.php', - - # includes/password - 'BcryptPassword' => 'includes/password/BcryptPassword.php', - 'InvalidPassword' => 'includes/password/InvalidPassword.php', - 'LayeredParameterizedPassword' => 'includes/password/LayeredParameterizedPassword.php', - 'MWSaltedPassword' => 'includes/password/MWSaltedPassword.php', - 'MWOldPassword' => 'includes/password/MWOldPassword.php', - 'ParameterizedPassword' => 'includes/password/ParameterizedPassword.php', - 'Password' => 'includes/password/Password.php', - 'PasswordError' => 'includes/password/PasswordError.php', - 'PasswordFactory' => 'includes/password/PasswordFactory.php', - 'Pbkdf2Password' => 'includes/password/Pbkdf2Password.php', - 'EncryptedPassword' => 'includes/password/EncryptedPassword.php', - - # includes/profiler - 'Profiler' => 'includes/profiler/Profiler.php', - 'ProfilerMwprof' => 'includes/profiler/ProfilerMwprof.php', - 'ProfilerSimpleDB' => 'includes/profiler/ProfilerSimpleDB.php', - 'ProfilerSimpleText' => 'includes/profiler/ProfilerSimpleText.php', - 'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php', - 'ProfilerSimpleUDP' => 'includes/profiler/ProfilerSimpleUDP.php', - 'ProfilerStandard' => 'includes/profiler/ProfilerStandard.php', - 'ProfilerStub' => 'includes/profiler/ProfilerStub.php', - 'ProfileSection' => 'includes/profiler/Profiler.php', - 'TransactionProfiler' => 'includes/profiler/Profiler.php', - - # includes/rcfeed - 'RCFeedEngine' => 'includes/rcfeed/RCFeedEngine.php', - 'RedisPubSubFeedEngine' => 'includes/rcfeed/RedisPubSubFeedEngine.php', - 'UDPRCFeedEngine' => 'includes/rcfeed/UDPRCFeedEngine.php', - 'RCFeedFormatter' => 'includes/rcfeed/RCFeedFormatter.php', - 'IRCColourfulRCFeedFormatter' => 'includes/rcfeed/IRCColourfulRCFeedFormatter.php', - 'JSONRCFeedFormatter' => 'includes/rcfeed/JSONRCFeedFormatter.php', - 'XMLRCFeedFormatter' => 'includes/rcfeed/XMLRCFeedFormatter.php', - 'MachineReadableRCFeedFormatter' => 'includes/rcfeed/MachineReadableRCFeedFormatter.php', - - # includes/resourceloader - 'DerivativeResourceLoaderContext' => - 'includes/resourceloader/DerivativeResourceLoaderContext.php', - 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php', - 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php', - 'ResourceLoaderEditToolbarModule' => 'includes/resourceloader/ResourceLoaderEditToolbarModule.php', - 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php', - 'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php', - 'ResourceLoaderFilePath' => 'includes/resourceloader/ResourceLoaderFilePath.php', - 'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php', - 'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php', - 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php', - 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php', - 'ResourceLoaderUserCSSPrefsModule' => - 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php', - 'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php', - 'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php', - 'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php', - 'ResourceLoaderUserTokensModule' => 'includes/resourceloader/ResourceLoaderUserTokensModule.php', - 'ResourceLoaderLanguageDataModule' => - 'includes/resourceloader/ResourceLoaderLanguageDataModule.php', - 'ResourceLoaderLanguageNamesModule' => - 'includes/resourceloader/ResourceLoaderLanguageNamesModule.php', - 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php', - - # includes/revisiondelete - 'RevDelArchivedFileItem' => 'includes/revisiondelete/RevDelArchivedFileItem.php', - 'RevDelArchivedFileList' => 'includes/revisiondelete/RevDelArchivedFileList.php', - 'RevDelArchivedRevisionItem' => 'includes/revisiondelete/RevDelArchivedRevisionItem.php', - 'RevDelArchiveItem' => 'includes/revisiondelete/RevDelArchiveItem.php', - 'RevDelArchiveList' => 'includes/revisiondelete/RevDelArchiveList.php', - 'RevDelFileItem' => 'includes/revisiondelete/RevDelFileItem.php', - 'RevDelFileList' => 'includes/revisiondelete/RevDelFileList.php', - 'RevDelItem' => 'includes/revisiondelete/RevDelItem.php', - 'RevDelList' => 'includes/revisiondelete/RevDelList.php', - 'RevDelLogItem' => 'includes/revisiondelete/RevDelLogItem.php', - 'RevDelLogList' => 'includes/revisiondelete/RevDelLogList.php', - 'RevDelRevisionItem' => 'includes/revisiondelete/RevDelRevisionItem.php', - 'RevDelRevisionList' => 'includes/revisiondelete/RevDelRevisionList.php', - 'RevisionDeleter' => 'includes/revisiondelete/RevisionDeleter.php', - 'RevisionDeleteUser' => 'includes/revisiondelete/RevisionDeleteUser.php', - - # includes/search - 'SearchDatabase' => 'includes/search/SearchDatabase.php', - 'SearchEngine' => 'includes/search/SearchEngine.php', - 'SearchEngineDummy' => 'includes/search/SearchEngine.php', - 'SearchHighlighter' => 'includes/search/SearchHighlighter.php', - 'SearchMssql' => 'includes/search/SearchMssql.php', - 'SearchMySQL' => 'includes/search/SearchMySQL.php', - 'SearchNearMatchResultSet' => 'includes/search/SearchResultSet.php', - 'SearchOracle' => 'includes/search/SearchOracle.php', - 'SearchPostgres' => 'includes/search/SearchPostgres.php', - 'SearchResult' => 'includes/search/SearchResult.php', - 'SearchResultSet' => 'includes/search/SearchResultSet.php', - 'SearchSqlite' => 'includes/search/SearchSqlite.php', - 'SqlSearchResultSet' => 'includes/search/SearchResultSet.php', - - # includes/site - 'MediaWikiSite' => 'includes/site/MediaWikiSite.php', - 'Site' => 'includes/site/Site.php', - 'SiteObject' => 'includes/site/Site.php', - 'SiteArray' => 'includes/site/SiteList.php', - 'SiteList' => 'includes/site/SiteList.php', - 'SiteSQLStore' => 'includes/site/SiteSQLStore.php', - 'Sites' => 'includes/site/SiteSQLStore.php', - 'SiteStore' => 'includes/site/SiteStore.php', - - # includes/skins - 'BaseTemplate' => 'includes/skins/SkinTemplate.php', - 'MediaWikiI18N' => 'includes/skins/SkinTemplate.php', - 'QuickTemplate' => 'includes/skins/SkinTemplate.php', - 'Skin' => 'includes/skins/Skin.php', - 'SkinException' => 'includes/skins/SkinException.php', - 'SkinFactory' => 'includes/skins/SkinFactory.php', - 'SkinFallback' => 'includes/skins/SkinFallback.php', - 'SkinFallbackTemplate' => 'includes/skins/SkinFallbackTemplate.php', - 'SkinTemplate' => 'includes/skins/SkinTemplate.php', - - # includes/specialpage - 'ChangesListSpecialPage' => 'includes/specialpage/ChangesListSpecialPage.php', - 'FormSpecialPage' => 'includes/specialpage/FormSpecialPage.php', - 'ImageQueryPage' => 'includes/specialpage/ImageQueryPage.php', - 'IncludableSpecialPage' => 'includes/specialpage/IncludableSpecialPage.php', - 'PageQueryPage' => 'includes/specialpage/PageQueryPage.php', - 'QueryPage' => 'includes/specialpage/QueryPage.php', - 'RedirectSpecialArticle' => 'includes/specialpage/RedirectSpecialPage.php', - 'RedirectSpecialPage' => 'includes/specialpage/RedirectSpecialPage.php', - 'SpecialPage' => 'includes/specialpage/SpecialPage.php', - 'SpecialPageFactory' => 'includes/specialpage/SpecialPageFactory.php', - 'SpecialRedirectToSpecial' => 'includes/specialpage/RedirectSpecialPage.php', - 'UnlistedSpecialPage' => 'includes/specialpage/UnlistedSpecialPage.php', - 'WantedQueryPage' => 'includes/specialpage/WantedQueryPage.php', - - # includes/specials - 'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php', - 'AllMessagesTablePager' => 'includes/specials/SpecialAllMessages.php', - 'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php', - 'BlockListPager' => 'includes/specials/SpecialBlockList.php', - 'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php', - 'CategoryPager' => 'includes/specials/SpecialCategories.php', - 'ContribsPager' => 'includes/specials/SpecialContributions.php', - 'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php', - 'DeletedContribsPager' => 'includes/specials/SpecialDeletedContributions.php', - 'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php', - 'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php', - 'EditWatchlistCheckboxSeriesField' => 'includes/specials/SpecialEditWatchlist.php', - 'EditWatchlistNormalHTMLForm' => 'includes/specials/SpecialEditWatchlist.php', - 'EmailConfirmation' => 'includes/specials/SpecialConfirmemail.php', - 'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php', - 'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php', - 'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php', - 'ImageListPager' => 'includes/specials/SpecialListfiles.php', - 'ImportReporter' => 'includes/specials/SpecialImport.php', - 'LinkSearchPage' => 'includes/specials/SpecialLinkSearch.php', - 'ListredirectsPage' => 'includes/specials/SpecialListredirects.php', - 'ListDuplicatedFilesPage' => 'includes/specials/SpecialListDuplicatedFiles.php', - 'LoginForm' => 'includes/specials/SpecialUserlogin.php', - 'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php', - 'LongPagesPage' => 'includes/specials/SpecialLongpages.php', - 'MediaStatisticsPage' => 'includes/specials/SpecialMediaStatistics.php', - 'MergeHistoryPager' => 'includes/specials/SpecialMergeHistory.php', - 'MIMEsearchPage' => 'includes/specials/SpecialMIMEsearch.php', - 'MostcategoriesPage' => 'includes/specials/SpecialMostcategories.php', - 'MostimagesPage' => 'includes/specials/SpecialMostimages.php', - 'MostinterwikisPage' => 'includes/specials/SpecialMostinterwikis.php', - 'MostlinkedCategoriesPage' => 'includes/specials/SpecialMostlinkedcategories.php', - 'MostlinkedPage' => 'includes/specials/SpecialMostlinked.php', - 'MostlinkedTemplatesPage' => 'includes/specials/SpecialMostlinkedtemplates.php', - 'MostrevisionsPage' => 'includes/specials/SpecialMostrevisions.php', - 'MovePageForm' => 'includes/specials/SpecialMovepage.php', - 'NewFilesPager' => 'includes/specials/SpecialNewimages.php', - 'NewPagesPager' => 'includes/specials/SpecialNewpages.php', - 'PageArchive' => 'includes/specials/SpecialUndelete.php', - 'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php', - 'ProtectedPagesPager' => 'includes/specials/SpecialProtectedpages.php', - 'ProtectedTitlesPager' => 'includes/specials/SpecialProtectedtitles.php', - 'RandomPage' => 'includes/specials/SpecialRandompage.php', - 'ShortPagesPage' => 'includes/specials/SpecialShortpages.php', - 'SpecialActiveUsers' => 'includes/specials/SpecialActiveusers.php', - 'SpecialAllMessages' => 'includes/specials/SpecialAllMessages.php', - 'SpecialAllMyUploads' => 'includes/specials/SpecialMyRedirectPages.php', - 'SpecialAllPages' => 'includes/specials/SpecialAllPages.php', - 'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php', - 'SpecialBlock' => 'includes/specials/SpecialBlock.php', - 'SpecialBlockList' => 'includes/specials/SpecialBlockList.php', - 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php', - 'SpecialCachedPage' => 'includes/specials/SpecialCachedPage.php', - 'SpecialCategories' => 'includes/specials/SpecialCategories.php', - 'SpecialChangeEmail' => 'includes/specials/SpecialChangeEmail.php', - 'SpecialChangePassword' => 'includes/specials/SpecialChangePassword.php', - 'SpecialComparePages' => 'includes/specials/SpecialComparePages.php', - 'SpecialContributions' => 'includes/specials/SpecialContributions.php', - 'SpecialCreateAccount' => 'includes/specials/SpecialCreateAccount.php', - 'SpecialDiff' => 'includes/specials/SpecialDiff.php', - 'SpecialEditWatchlist' => 'includes/specials/SpecialEditWatchlist.php', - 'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php', - 'SpecialExpandTemplates' => 'includes/specials/SpecialExpandTemplates.php', - 'SpecialExport' => 'includes/specials/SpecialExport.php', - 'SpecialFilepath' => 'includes/specials/SpecialFilepath.php', - 'SpecialImport' => 'includes/specials/SpecialImport.php', - 'SpecialJavaScriptTest' => 'includes/specials/SpecialJavaScriptTest.php', - 'SpecialListAdmins' => 'includes/specials/SpecialListusers.php', - 'SpecialListBots' => 'includes/specials/SpecialListusers.php', - 'SpecialListFiles' => 'includes/specials/SpecialListfiles.php', - 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', - 'SpecialListUsers' => 'includes/specials/SpecialListusers.php', - 'SpecialLockdb' => 'includes/specials/SpecialLockdb.php', - 'SpecialLog' => 'includes/specials/SpecialLog.php', - 'SpecialMergeHistory' => 'includes/specials/SpecialMergeHistory.php', - 'SpecialMycontributions' => 'includes/specials/SpecialMyRedirectPages.php', - 'SpecialMyLanguage' => 'includes/specials/SpecialMyLanguage.php', - 'SpecialMypage' => 'includes/specials/SpecialMyRedirectPages.php', - 'SpecialMytalk' => 'includes/specials/SpecialMyRedirectPages.php', - 'SpecialMyuploads' => 'includes/specials/SpecialMyRedirectPages.php', - 'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php', - 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php', - 'SpecialPageLanguage' => 'includes/specials/SpecialPageLanguage.php', - 'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php', - 'SpecialPagesWithProp' => 'includes/specials/SpecialPagesWithProp.php', - 'SpecialPermanentLink' => 'includes/specials/SpecialPermanentLink.php', - 'SpecialPreferences' => 'includes/specials/SpecialPreferences.php', - 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php', - 'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php', - 'SpecialProtectedtitles' => 'includes/specials/SpecialProtectedtitles.php', - 'SpecialRandomInCategory' => 'includes/specials/SpecialRandomInCategory.php', - 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php', - 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php', - 'SpecialRecentChangesLinked' => 'includes/specials/SpecialRecentchangeslinked.php', - 'SpecialRedirect' => 'includes/specials/SpecialRedirect.php', - 'SpecialResetTokens' => 'includes/specials/SpecialResetTokens.php', - 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php', - 'SpecialRunJobs' => 'includes/specials/SpecialRunJobs.php', - 'SpecialSearch' => 'includes/specials/SpecialSearch.php', - 'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php', - 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php', - 'SpecialTags' => 'includes/specials/SpecialTags.php', - 'SpecialTrackingCategories' => 'includes/specials/SpecialTrackingCategories.php', - 'SpecialUnblock' => 'includes/specials/SpecialUnblock.php', - 'SpecialUndelete' => 'includes/specials/SpecialUndelete.php', - 'SpecialUnlockdb' => 'includes/specials/SpecialUnlockdb.php', - 'SpecialUpload' => 'includes/specials/SpecialUpload.php', - 'SpecialUploadStash' => 'includes/specials/SpecialUploadStash.php', - 'SpecialUploadStashTooLargeException' => 'includes/specials/SpecialUploadStash.php', - 'SpecialUserlogout' => 'includes/specials/SpecialUserlogout.php', - 'SpecialVersion' => 'includes/specials/SpecialVersion.php', - 'SpecialWatchlist' => 'includes/specials/SpecialWatchlist.php', - 'SpecialWhatLinksHere' => 'includes/specials/SpecialWhatlinkshere.php', - 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php', - 'UncategorizedImagesPage' => 'includes/specials/SpecialUncategorizedimages.php', - 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php', - 'UncategorizedTemplatesPage' => 'includes/specials/SpecialUncategorizedtemplates.php', - 'UnusedCategoriesPage' => 'includes/specials/SpecialUnusedcategories.php', - 'UnusedimagesPage' => 'includes/specials/SpecialUnusedimages.php', - 'UnusedtemplatesPage' => 'includes/specials/SpecialUnusedtemplates.php', - 'UnwatchedpagesPage' => 'includes/specials/SpecialUnwatchedpages.php', - 'UploadChunkFileException' => 'includes/upload/UploadFromChunks.php', - 'UploadChunkZeroLengthFileException' => 'includes/upload/UploadFromChunks.php', - 'UploadChunkVerificationException' => 'includes/upload/UploadFromChunks.php', - 'UploadForm' => 'includes/specials/SpecialUpload.php', - 'UploadSourceField' => 'includes/specials/SpecialUpload.php', - 'UserrightsPage' => 'includes/specials/SpecialUserrights.php', - 'UsersPager' => 'includes/specials/SpecialListusers.php', - 'WantedCategoriesPage' => 'includes/specials/SpecialWantedcategories.php', - 'WantedFilesPage' => 'includes/specials/SpecialWantedfiles.php', - 'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php', - 'WantedTemplatesPage' => 'includes/specials/SpecialWantedtemplates.php', - 'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php', - - # includes/templates - 'UserloginTemplate' => 'includes/templates/Userlogin.php', - 'UsercreateTemplate' => 'includes/templates/Usercreate.php', - - # includes/title - 'PageLinkRenderer' => 'includes/title/PageLinkRenderer.php', - 'TitleFormatter' => 'includes/title/TitleFormatter.php', - 'TitleParser' => 'includes/title/TitleParser.php', - 'TitleValue' => 'includes/title/TitleValue.php', - 'MalformedTitleException' => 'includes/title/MalformedTitleException.php', - 'MediaWikiPageLinkRenderer' => 'includes/title/MediaWikiPageLinkRenderer.php', - 'MediaWikiTitleCodec' => 'includes/title/MediaWikiTitleCodec.php', - - # includes/upload - 'UploadBase' => 'includes/upload/UploadBase.php', - 'UploadFromFile' => 'includes/upload/UploadFromFile.php', - 'UploadFromChunks' => 'includes/upload/UploadFromChunks.php', - 'UploadFromStash' => 'includes/upload/UploadFromStash.php', - 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php', - 'UploadStash' => 'includes/upload/UploadStash.php', - 'UploadStashBadPathException' => 'includes/upload/UploadStash.php', - 'UploadStashException' => 'includes/upload/UploadStash.php', - 'UploadStashFile' => 'includes/upload/UploadStash.php', - 'UploadStashFileException' => 'includes/upload/UploadStash.php', - 'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php', - 'UploadStashNotAvailableException' => 'includes/upload/UploadStash.php', - 'UploadStashZeroLengthFileException' => 'includes/upload/UploadStash.php', - 'UploadStashNotLoggedInException' => 'includes/upload/UploadStash.php', - 'UploadStashWrongOwnerException' => 'includes/upload/UploadStash.php', - 'UploadStashNoSuchKeyException' => 'includes/upload/UploadStash.php', - - # includes/utils - 'ArrayUtils' => 'includes/utils/ArrayUtils.php', - 'CdbException' => 'includes/utils/Cdb.php', - 'CdbFunctions' => 'includes/utils/CdbPHP.php', - 'CdbReader' => 'includes/utils/Cdb.php', - 'CdbReaderDBA' => 'includes/utils/CdbDBA.php', - 'CdbReaderPHP' => 'includes/utils/CdbPHP.php', - 'CdbWriter' => 'includes/utils/Cdb.php', - 'CdbWriterDBA' => 'includes/utils/CdbDBA.php', - 'CdbWriterPHP' => 'includes/utils/CdbPHP.php', - 'DoubleReplacer' => 'includes/utils/StringUtils.php', - 'ExplodeIterator' => 'includes/utils/StringUtils.php', - 'HashtableReplacer' => 'includes/utils/StringUtils.php', - 'IP' => 'includes/utils/IP.php', - 'MWCryptRand' => 'includes/utils/MWCryptRand.php', - 'MWCryptHKDF' => 'includes/utils/MWCryptHKDF.php', - 'MWFunction' => 'includes/utils/MWFunction.php', - 'RegexlikeReplacer' => 'includes/utils/StringUtils.php', - 'ReplacementArray' => 'includes/utils/StringUtils.php', - 'Replacer' => 'includes/utils/StringUtils.php', - 'StringUtils' => 'includes/utils/StringUtils.php', - 'UIDGenerator' => 'includes/utils/UIDGenerator.php', - 'ZipDirectoryReader' => 'includes/utils/ZipDirectoryReader.php', - 'ZipDirectoryReaderError' => 'includes/utils/ZipDirectoryReader.php', - - # languages - 'ConverterRule' => 'languages/ConverterRule.php', - 'FakeConverter' => 'languages/FakeConverter.php', - 'Language' => 'languages/Language.php', - 'LanguageConverter' => 'languages/LanguageConverter.php', - 'CLDRPluralRuleConverter' => 'languages/utils/CLDRPluralRuleConverter.php', - 'CLDRPluralRuleConverterExpression' => 'languages/utils/CLDRPluralRuleConverterExpression.php', - 'CLDRPluralRuleConverterFragment' => 'languages/utils/CLDRPluralRuleConverterFragment.php', - 'CLDRPluralRuleConverterOperator' => 'languages/utils/CLDRPluralRuleConverterOperator.php', - 'CLDRPluralRuleEvaluator' => 'languages/utils/CLDRPluralRuleEvaluator.php', - 'CLDRPluralRuleEvaluatorRange' => 'languages/utils/CLDRPluralRuleEvaluatorRange.php', - 'CLDRPluralRuleError' => 'languages/utils/CLDRPluralRuleError.php', - - # maintenance - 'BackupDumper' => 'maintenance/backup.inc', - 'ConvertLinks' => 'maintenance/convertLinks.php', - 'DeleteArchivedFilesImplementation' => 'maintenance/deleteArchivedFiles.inc', - 'DeleteArchivedRevisionsImplementation' => 'maintenance/deleteArchivedRevisions.inc', - 'DeleteDefaultMessages' => 'maintenance/deleteDefaultMessages.php', - 'DumpDBZip2Output' => 'maintenance/backup.inc', - 'ExportProgressFilter' => 'maintenance/backup.inc', - 'FakeMaintenance' => 'maintenance/Maintenance.php', - 'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php', - 'LoggedUpdateMaintenance' => 'maintenance/Maintenance.php', - 'Maintenance' => 'maintenance/Maintenance.php', - 'PopulateBacklinkNamespace' => 'maintenance/populateBacklinkNamespace.php', - 'PopulateCategory' => 'maintenance/populateCategory.php', - 'PopulateImageSha1' => 'maintenance/populateImageSha1.php', - 'PopulateFilearchiveSha1' => 'maintenance/populateFilearchiveSha1.php', - 'PopulateLogSearch' => 'maintenance/populateLogSearch.php', - 'PopulateLogUsertext' => 'maintenance/populateLogUsertext.php', - 'PopulateParentId' => 'maintenance/populateParentId.php', - 'PopulateRevisionLength' => 'maintenance/populateRevisionLength.php', - 'PopulateRevisionSha1' => 'maintenance/populateRevisionSha1.php', - 'RefreshLinks' => 'maintenance/refreshLinks.php', - 'SevenZipStream' => 'maintenance/7zip.inc', - 'Sqlite' => 'maintenance/sqlite.inc', - 'UpdateCollation' => 'maintenance/updateCollation.php', - 'UpdateRestrictions' => 'maintenance/updateRestrictions.php', - 'UserDupes' => 'maintenance/userDupes.inc', - - # maintenance/language - 'CsvStatsOutput' => 'maintenance/language/StatOutputs.php', - 'ExtensionLanguages' => 'maintenance/language/languages.inc', - 'Languages' => 'maintenance/language/languages.inc', - 'StatsOutput' => 'maintenance/language/StatOutputs.php', - 'TextStatsOutput' => 'maintenance/language/StatOutputs.php', - 'WikiStatsOutput' => 'maintenance/language/StatOutputs.php', - - # maintenance/term - 'AnsiTermColorer' => 'maintenance/term/MWTerm.php', - 'DummyTermColorer' => 'maintenance/term/MWTerm.php', - - # mw-config - 'InstallerOverrides' => 'mw-config/overrides.php', - 'MyLocalSettingsGenerator' => 'mw-config/overrides.php', -); +require_once __DIR__ . '/../autoload.php'; class AutoLoader { static protected $autoloadLocalClassesLower = null; diff --git a/includes/Autopromote.php b/includes/Autopromote.php index 81f3b7aa..d492d196 100644 --- a/includes/Autopromote.php +++ b/includes/Autopromote.php @@ -43,7 +43,7 @@ class Autopromote { } } - wfRunHooks( 'GetAutoPromoteGroups', array( $user, &$promote ) ); + Hooks::run( 'GetAutoPromoteGroups', array( $user, &$promote ) ); return $promote; } @@ -197,7 +197,7 @@ class Autopromote { return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) ); default: $result = null; - wfRunHooks( 'AutopromoteCondition', array( $cond[0], + Hooks::run( 'AutopromoteCondition', array( $cond[0], array_slice( $cond, 1 ), $user, &$result ) ); if ( $result === null ) { throw new MWException( "Unrecognized condition {$cond[0]} for autopromotion!" ); diff --git a/includes/Block.php b/includes/Block.php index 6a29a056..873a26d8 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -113,7 +113,7 @@ class Block { $this->forcedTargetID = $user; // needed for foreign users } if ( $by ) { // local user - $this->setBlocker( User::newFromID( $by ) ); + $this->setBlocker( User::newFromId( $by ) ); } else { // foreign user $this->setBlocker( $byText ); } @@ -366,7 +366,7 @@ class Block { protected function initFromRow( $row ) { $this->setTarget( $row->ipb_address ); if ( $row->ipb_by ) { // local user - $this->setBlocker( User::newFromID( $row->ipb_by ) ); + $this->setBlocker( User::newFromId( $row->ipb_by ) ); } else { // foreign user $this->setBlocker( $row->ipb_by_text ); } @@ -442,19 +442,33 @@ class Block { $dbw = wfGetDB( DB_MASTER ); } - # Don't collide with expired blocks - Block::purgeExpired(); + # Periodic purge via commit hooks + if ( mt_rand( 0, 9 ) == 0 ) { + Block::purgeExpired(); + } $row = $this->getDatabaseArray(); $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" ); - $dbw->insert( - 'ipblocks', - $row, - __METHOD__, - array( 'IGNORE' ) - ); + $dbw->insert( 'ipblocks', $row, __METHOD__, array( 'IGNORE' ) ); $affected = $dbw->affectedRows(); + + # Don't collide with expired blocks. + # Do this after trying to insert to avoid pointless gap locks. + if ( !$affected ) { + $dbw->delete( 'ipblocks', + array( + 'ipb_address' => $row['ipb_address'], + 'ipb_user' => $row['ipb_user'], + 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) + ), + __METHOD__ + ); + + $dbw->insert( 'ipblocks', $row, __METHOD__, array( 'IGNORE' ) ); + $affected = $dbw->affectedRows(); + } + $this->mId = $dbw->insertId(); if ( $affected ) { @@ -580,7 +594,7 @@ class Block { if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) { wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" ); - $continue = wfRunHooks( + $continue = Hooks::run( 'PerformRetroactiveAutoblock', array( $this, &$blockIds ) ); if ( $continue ) { @@ -693,7 +707,7 @@ class Block { } # Allow hooks to cancel the autoblock. - if ( !wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) { + if ( !Hooks::run( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) { wfDebug( "Autoblock aborted by hook.\n" ); return false; } @@ -752,7 +766,6 @@ class Block { * @return bool */ public function deleteIfExpired() { - wfProfileIn( __METHOD__ ); if ( $this->isExpired() ) { wfDebug( "Block::deleteIfExpired() -- deleting\n" ); @@ -763,7 +776,6 @@ class Block { $retVal = false; } - wfProfileOut( __METHOD__ ); return $retVal; } @@ -885,7 +897,7 @@ class Block { /** * Get/set a flag determining whether the master is used for reads * - * @param bool $x + * @param bool|null $x * @return bool */ public function fromMaster( $x = null ) { @@ -893,8 +905,8 @@ class Block { } /** - * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range - * @param bool $x + * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range) + * @param bool|null $x * @return bool */ public function isHardblock( $x = null ) { @@ -906,6 +918,10 @@ class Block { : $this->isHardblock; } + /** + * @param null|bool $x + * @return bool + */ public function isAutoblocking( $x = null ) { wfSetVar( $this->isAutoblocking, $x ); @@ -919,7 +935,7 @@ class Block { /** * Get/set whether the Block prevents a given action * @param string $action - * @param bool $x + * @param bool|null $x * @return bool */ public function prevents( $action, $x = null ) { @@ -1051,7 +1067,6 @@ class Block { return array(); } - wfProfileIn( __METHOD__ ); $conds = array(); foreach ( array_unique( $ipChain ) as $ipaddr ) { # Discard invalid IP addresses. Since XFF can be spoofed and we do not @@ -1073,7 +1088,6 @@ class Block { } if ( !count( $conds ) ) { - wfProfileOut( __METHOD__ ); return array(); } @@ -1104,12 +1118,12 @@ class Block { } } - wfProfileOut( __METHOD__ ); return $blocks; } /** * From a list of multiple blocks, find the most exact and strongest Block. + * * The logic for finding the "best" block is: * - Blocks that match the block's target IP are preferred over ones in a range * - Hardblocks are chosen over softblocks that prevent account creation @@ -1117,12 +1131,15 @@ class Block { * - Other softblocks are chosen over autoblocks * - If there are multiple exact or range blocks at the same level, the one chosen * is random + * This should be used when $blocks where retrieved from the user's IP address + * and $ipChain is populated from the same IP address information. * - * @param array $blocks Array of blocks + * @param array $blocks Array of Block objects * @param array $ipChain List of IPs (strings). This is used to determine how "close" * a block is to the server, and if a block matches exactly, or is in a range. * The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2, * local-squid, ...) + * @throws MWException * @return Block|null The "best" block from the list */ public static function chooseBlock( array $blocks, array $ipChain ) { @@ -1132,8 +1149,6 @@ class Block { return $blocks[0]; } - wfProfileIn( __METHOD__ ); - // Sort hard blocks before soft ones and secondarily sort blocks // that disable account creation before those that don't. usort( $blocks, function ( Block $a, Block $b ) { @@ -1156,6 +1171,7 @@ class Block { ); $ipChain = array_reverse( $ipChain ); + /** @var Block $block */ foreach ( $blocks as $block ) { // Stop searching if we have already have a "better" block. This // is why the order of the blocks matters @@ -1213,11 +1229,9 @@ class Block { } elseif ( $blocksList['auto'] ) { $chosenBlock = $blocksList['auto']; } else { - wfProfileOut( __METHOD__ ); throw new MWException( "Proxy block found, but couldn't be classified." ); } - wfProfileOut( __METHOD__ ); return $chosenBlock; } diff --git a/includes/Category.php b/includes/Category.php index 322b0530..3a21e256 100644 --- a/includes/Category.php +++ b/includes/Category.php @@ -60,8 +60,6 @@ class Category { return true; } - wfProfileIn( __METHOD__ ); - $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'category', @@ -70,8 +68,6 @@ class Category { __METHOD__ ); - wfProfileOut( __METHOD__ ); - if ( !$row ) { # Okay, there were no contents. Nothing to initialize. if ( $this->mTitle ) { @@ -258,7 +254,6 @@ class Category { * @return TitleArray TitleArray object for category members. */ public function getMembers( $limit = false, $offset = '' ) { - wfProfileIn( __METHOD__ ); $dbr = wfGetDB( DB_SLAVE ); @@ -284,8 +279,6 @@ class Category { ) ); - wfProfileOut( __METHOD__ ); - return $result; } @@ -318,8 +311,6 @@ class Category { } } - wfProfileIn( __METHOD__ ); - $dbw = wfGetDB( DB_MASTER ); $dbw->startAtomic( __METHOD__ ); @@ -363,8 +354,6 @@ class Category { ); $dbw->endAtomic( __METHOD__ ); - wfProfileOut( __METHOD__ ); - # Now we should update our local counts. $this->mPages = $result->pages; $this->mSubcats = $result->subcats; diff --git a/includes/CategoryFinder.php b/includes/CategoryFinder.php index cf537e15..33de7404 100644 --- a/includes/CategoryFinder.php +++ b/includes/CategoryFinder.php @@ -185,7 +185,6 @@ class CategoryFinder { * Scans a "parent layer" of the articles/categories in $this->next */ private function scanNextLayer() { - $profiler = new ProfileSection( __METHOD__ ); # Find all parents of the article currently in $this->next $layer = array(); diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index 7581ae40..66079c01 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -89,6 +89,9 @@ class CategoryViewer extends ContextSource { ) { $this->title = $title; $this->setContext( $context ); + $this->getOutput()->addModuleStyles( array( + 'mediawiki.action.view.categoryPage.styles' + ) ); $this->from = $from; $this->until = $until; $this->limit = $context->getConfig()->get( 'CategoryPagingLimit' ); @@ -104,7 +107,6 @@ class CategoryViewer extends ContextSource { * @return string HTML output */ public function getHTML() { - wfProfileIn( __METHOD__ ); $this->showGallery = $this->getConfig()->get( 'CategoryMagicGallery' ) && !$this->getOutput()->mNoGallery; @@ -136,11 +138,10 @@ class CategoryViewer extends ContextSource { } $lang = $this->getLanguage(); - $langAttribs = array( 'lang' => $lang->getCode(), 'dir' => $lang->getDir() ); + $langAttribs = array( 'lang' => $lang->getHtmlCode(), 'dir' => $lang->getDir() ); # put a div around the headings which are in the user language $r = Html::openElement( 'div', $langAttribs ) . $r . ''; - wfProfileOut( __METHOD__ ); return $r; } @@ -154,7 +155,7 @@ class CategoryViewer extends ContextSource { $mode = $this->getRequest()->getVal( 'gallerymode', null ); try { $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { // User specified something invalid, fallback to default. $this->gallery = ImageGalleryBase::factory( false, $this->getContext() ); } @@ -176,19 +177,30 @@ class CategoryViewer extends ContextSource { // Subcategory; strip the 'Category' namespace from the link text. $title = $cat->getTitle(); - $link = Linker::link( $title, htmlspecialchars( $title->getText() ) ); - if ( $title->isRedirect() ) { - // This didn't used to add redirect-in-category, but might - // as well be consistent with the rest of the sections - // on a category page. - $link = '' . $link . ''; - } - $this->children[] = $link; + $this->children[] = $this->generateLink( + 'subcat', + $title, + $title->isRedirect(), + htmlspecialchars( $title->getText() ) + ); $this->children_start_char[] = $this->getSubcategorySortChar( $cat->getTitle(), $sortkey ); } + function generateLink( $type, Title $title, $isRedirect, $html = null ) { + $link = null; + Hooks::run( 'CategoryViewer::generateLink', array( $type, $title, $html, &$link ) ); + if ( $link === null ) { + $link = Linker::link( $title, $html ); + } + if ( $isRedirect ) { + $link = '' . $link . ''; + } + + return $link; + } + /** * Get the character to be used for sorting subcategories. * If there's a link from Category:A to Category:B, the sortkey of the resulting @@ -231,13 +243,7 @@ class CategoryViewer extends ContextSource { $this->gallery->add( $title ); } } else { - $link = Linker::link( $title ); - if ( $isRedirect ) { - // This seems kind of pointless given 'mw-redirect' class, - // but keeping for back-compatibility with user css. - $link = '' . $link . ''; - } - $this->imgsNoGallery[] = $link; + $this->imgsNoGallery[] = $this->generateLink( 'image', $title, $isRedirect ); $this->imgsNoGallery_start_char[] = $wgContLang->convert( $this->collation->getFirstLetter( $sortkey ) ); @@ -254,13 +260,7 @@ class CategoryViewer extends ContextSource { function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) { global $wgContLang; - $link = Linker::link( $title ); - if ( $isRedirect ) { - // This seems kind of pointless given 'mw-redirect' class, - // but keeping for back-compatibility with user css. - $link = '' . $link . ''; - } - $this->articles[] = $link; + $this->articles[] = $this->generateLink( 'page', $title, $isRedirect ); $this->articles_start_char[] = $wgContLang->convert( $this->collation->getFirstLetter( $sortkey ) ); @@ -333,6 +333,8 @@ class CategoryViewer extends ContextSource { ) ); + Hooks::run( 'CategoryViewer::doCategoryQuery', array( $type, $res ) ); + $count = 0; foreach ( $res as $row ) { $title = Title::newFromRow( $row ); @@ -390,7 +392,7 @@ class CategoryViewer extends ContextSource { if ( $rescnt > 0 ) { # Showing subcategories $r .= "
\n"; - $r .= '

' . $this->msg( 'subcategories' )->text() . "

\n"; + $r .= '

' . $this->msg( 'subcategories' )->parse() . "

\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'subcat' ); $r .= $this->formatList( $this->children, $this->children_start_char ); @@ -419,7 +421,7 @@ class CategoryViewer extends ContextSource { if ( $rescnt > 0 ) { $r = "
\n"; - $r .= '

' . $this->msg( 'category_header', $ti )->text() . "

\n"; + $r .= '

' . $this->msg( 'category_header', $ti )->parse() . "

\n"; $r .= $countmsg; $r .= $this->getSectionPagingLinks( 'page' ); $r .= $this->formatList( $this->articles, $this->articles_start_char ); @@ -515,7 +517,7 @@ class CategoryViewer extends ContextSource { } $pageLang = $this->title->getPageLanguage(); - $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(), + $attribs = array( 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(), 'class' => 'mw-content-' . $pageLang->getDir() ); $list = Html::rawElement( 'div', $attribs, $list ); @@ -529,8 +531,7 @@ class CategoryViewer extends ContextSource { * TODO: Take the headers into account when creating columns, so they're * more visually equal. * - * More distant TODO: Scrap this and use CSS columns, whenever IE finally - * supports those. + * TODO: shortList and columnList are similar, need merging * * @param array $articles * @param string[] $articles_start_char @@ -539,50 +540,34 @@ class CategoryViewer extends ContextSource { */ static function columnList( $articles, $articles_start_char ) { $columns = array_combine( $articles, $articles_start_char ); - # Split into three columns - $columns = array_chunk( $columns, ceil( count( $columns ) / 3 ), true /* preserve keys */ ); - $ret = ''; - $prevchar = null; + $ret = Html::openElement( 'div', array( 'class' => 'mw-category' ) ); - foreach ( $columns as $column ) { - $ret .= '\n"; } - $ret .= '
'; - $colContents = array(); + $colContents = array(); - # Kind of like array_flip() here, but we keep duplicates in an - # array instead of dropping them. - foreach ( $column as $article => $char ) { - if ( !isset( $colContents[$char] ) ) { - $colContents[$char] = array(); - } - $colContents[$char][] = $article; + # Kind of like array_flip() here, but we keep duplicates in an + # array instead of dropping them. + foreach ( $columns as $article => $char ) { + if ( !isset( $colContents[$char] ) ) { + $colContents[$char] = array(); } + $colContents[$char][] = $article; + } - $first = true; - foreach ( $colContents as $char => $articles ) { - # Change space to non-breaking space to keep headers aligned - $h3char = $char === ' ' ? ' ' : htmlspecialchars( $char ); + foreach ( $colContents as $char => $articles ) { + # Change space to non-breaking space to keep headers aligned + $h3char = $char === ' ' ? ' ' : htmlspecialchars( $char ); - $ret .= '

' . $h3char; - if ( $first && $char === $prevchar ) { - # We're continuing a previous chunk at the top of a new - # column, so add " cont." after the letter. - $ret .= ' ' . wfMessage( 'listingcontinuesabbrev' )->escaped(); - } - $ret .= "

\n"; + $ret .= '

' . $h3char; + $ret .= "

\n"; - $ret .= '
  • '; - $ret .= implode( "
  • \n
  • ", $articles ); - $ret .= '
'; - - $first = false; - $prevchar = $char; - } + $ret .= '
  • '; + $ret .= implode( "
  • \n
  • ", $articles ); + $ret .= '
'; - $ret .= "
'; + $ret .= Html::closeElement( 'div' ); return $ret; } @@ -618,7 +603,7 @@ class CategoryViewer extends ContextSource { * @return string HTML */ private function pagingLinks( $first, $last, $type = '' ) { - $prevLink = $this->msg( 'prevn' )->numParams( $this->limit )->escaped(); + $prevLink = $this->msg( 'prev-page' )->text(); if ( $first != '' ) { $prevQuery = $this->query; @@ -632,7 +617,7 @@ class CategoryViewer extends ContextSource { ); } - $nextLink = $this->msg( 'nextn' )->numParams( $this->limit )->escaped(); + $nextLink = $this->msg( 'next-page' )->text(); if ( $last != '' ) { $lastQuery = $this->query; diff --git a/includes/CdbCompat.php b/includes/CdbCompat.php new file mode 100644 index 00000000..0074cc96 --- /dev/null +++ b/includes/CdbCompat.php @@ -0,0 +1,45 @@ + $tag, + 'ct_rc_id' => $rc_id, + 'ct_log_id' => $log_id, + 'ct_rev_id' => $rev_id, + 'ct_params' => $params + ) + ); + } + + $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array( 'IGNORE' ) ); + } + + // delete from change_tag + if ( count( $tagsToRemove ) ) { + foreach ( $tagsToRemove as $tag ) { + $conds = array_filter( + array( + 'ct_tag' => $tag, + 'ct_rc_id' => $rc_id, + 'ct_log_id' => $log_id, + 'ct_rev_id' => $rev_id + ) + ); + $dbw->delete( 'change_tag', $conds, __METHOD__ ); + } + } + + self::purgeTagUsageCache(); + return array( $tagsToAdd, $tagsToRemove, $prevTags ); + } + + /** + * Adds or removes a given set of tags to/from the relevant row of the + * tag_summary table. Modifies the tagsToAdd and tagsToRemove arrays to + * reflect the tags that were actually added and/or removed. + * + * @param array &$tagsToAdd + * @param array &$tagsToRemove If a tag is present in both $tagsToAdd and + * $tagsToRemove, it will be removed + * @param int|null $rc_id Null if not known or not applicable + * @param int|null $rev_id Null if not known or not applicable + * @param int|null $log_id Null if not known or not applicable + * @param array &$prevTags Optionally outputs a list of the tags that were + * in the tag_summary row to begin with + * @return bool True if any modifications were made, otherwise false + * @since 1.25 + */ + protected static function updateTagSummaryRow( &$tagsToAdd, &$tagsToRemove, + $rc_id, $rev_id, $log_id, &$prevTags = array() ) { + + $dbw = wfGetDB( DB_MASTER ); + $tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, - 'ts_log_id' => $log_id ) - ); + 'ts_log_id' => $log_id + ) ); + + // Can't both add and remove a tag at the same time... + $tagsToAdd = array_diff( $tagsToAdd, $tagsToRemove ); // Update the summary row. // $prevTags can be out of date on slaves, especially when addTags is called consecutively, @@ -149,41 +260,276 @@ class ChangeTags { $prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ ); $prevTags = $prevTags ? $prevTags : ''; $prevTags = array_filter( explode( ',', $prevTags ) ); - $newTags = array_unique( array_merge( $prevTags, $tags ) ); + + // add tags + $tagsToAdd = array_values( array_diff( $tagsToAdd, $prevTags ) ); + $newTags = array_unique( array_merge( $prevTags, $tagsToAdd ) ); + + // remove tags + $tagsToRemove = array_values( array_intersect( $tagsToRemove, $newTags ) ); + $newTags = array_values( array_diff( $newTags, $tagsToRemove ) ); + sort( $prevTags ); sort( $newTags ); - if ( $prevTags == $newTags ) { // No change. return false; } - $dbw->replace( - 'tag_summary', - array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ), - array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ), - __METHOD__ - ); - - // Insert the tags rows. - $tagsRows = array(); - foreach ( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally. - $tagsRows[] = array_filter( - array( - 'ct_tag' => $tag, - 'ct_rc_id' => $rc_id, - 'ct_log_id' => $log_id, - 'ct_rev_id' => $rev_id, - 'ct_params' => $params - ) + if ( !$newTags ) { + // no tags left, so delete the row altogether + $dbw->delete( 'tag_summary', $tsConds, __METHOD__ ); + } else { + $dbw->replace( 'tag_summary', + array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ), + array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ), + __METHOD__ ); } - $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array( 'IGNORE' ) ); - return true; } + /** + * Helper function to generate a fatal status with a 'not-allowed' type error. + * + * @param string $msgOne Message key to use in the case of one tag + * @param string $msgMulti Message key to use in the case of more than one tag + * @param array $tags Restricted tags (passed as $1 into the message, count of + * $tags passed as $2) + * @return Status + * @since 1.25 + */ + protected static function restrictedTagError( $msgOne, $msgMulti, $tags ) { + $lang = RequestContext::getMain()->getLanguage(); + $count = count( $tags ); + return Status::newFatal( ( $count > 1 ) ? $msgMulti : $msgOne, + $lang->commaList( $tags ), $count ); + } + + /** + * Is it OK to allow the user to apply all the specified tags at the same time + * as they edit/make the change? + * + * @param array $tags Tags that you are interested in applying + * @param User|null $user User whose permission you wish to check, or null if + * you don't care (e.g. maintenance scripts) + * @return Status + * @since 1.25 + */ + public static function canAddTagsAccompanyingChange( array $tags, + User $user = null ) { + + if ( !is_null( $user ) && !$user->isAllowed( 'applychangetags' ) ) { + return Status::newFatal( 'tags-apply-no-permission' ); + } + + // to be applied, a tag has to be explicitly defined + // @todo Allow extensions to define tags that can be applied by users... + $allowedTags = self::listExplicitlyDefinedTags(); + $disallowedTags = array_diff( $tags, $allowedTags ); + if ( $disallowedTags ) { + return self::restrictedTagError( 'tags-apply-not-allowed-one', + 'tags-apply-not-allowed-multi', $disallowedTags ); + } + + return Status::newGood(); + } + + /** + * Adds tags to a given change, checking whether it is allowed first, but + * without adding a log entry. Useful for cases where the tag is being added + * along with the action that generated the change (e.g. tagging an edit as + * it is being made). + * + * Extensions should not use this function, unless directly handling a user + * request to add a particular tag. Normally, extensions should call + * ChangeTags::updateTags() instead. + * + * @param array $tags Tags to apply + * @param int|null $rc_id The rc_id of the change to add the tags to + * @param int|null $rev_id The rev_id of the change to add the tags to + * @param int|null $log_id The log_id of the change to add the tags to + * @param string $params Params to put in the ct_params field of table + * 'change_tag' when adding tags + * @param User $user Who to give credit for the action + * @return Status + * @since 1.25 + */ + public static function addTagsAccompanyingChangeWithChecks( array $tags, + $rc_id, $rev_id, $log_id, $params, User $user ) { + + // are we allowed to do this? + $result = self::canAddTagsAccompanyingChange( $tags, $user ); + if ( !$result->isOK() ) { + $result->value = null; + return $result; + } + + // do it! + self::addTags( $tagsToAdd, $rc_id, $rev_id, $log_id, $params ); + + return Status::newGood( true ); + } + + /** + * Is it OK to allow the user to adds and remove the given tags tags to/from a + * change? + * + * @param array $tagsToAdd Tags that you are interested in adding + * @param array $tagsToRemove Tags that you are interested in removing + * @param User|null $user User whose permission you wish to check, or null if + * you don't care (e.g. maintenance scripts) + * @return Status + * @since 1.25 + */ + public static function canUpdateTags( array $tagsToAdd, array $tagsToRemove, + User $user = null ) { + + if ( !is_null( $user ) && !$user->isAllowed( 'changetags' ) ) { + return Status::newFatal( 'tags-update-no-permission' ); + } + + // to be added, a tag has to be explicitly defined + // @todo Allow extensions to define tags that can be applied by users... + $explicitlyDefinedTags = self::listExplicitlyDefinedTags(); + $diff = array_diff( $tagsToAdd, $explicitlyDefinedTags ); + if ( $diff ) { + return self::restrictedTagError( 'tags-update-add-not-allowed-one', + 'tags-update-add-not-allowed-multi', $diff ); + } + + // to be removed, a tag has to be either explicitly defined or not defined + // at all + $definedTags = self::listDefinedTags(); + $diff = array_diff( $tagsToRemove, $explicitlyDefinedTags ); + if ( $diff ) { + $intersect = array_intersect( $diff, $definedTags ); + if ( $intersect ) { + return self::restrictedTagError( 'tags-update-remove-not-allowed-one', + 'tags-update-remove-not-allowed-multi', $intersect ); + } + } + + return Status::newGood(); + } + + /** + * Adds and/or removes tags to/from a given change, checking whether it is + * allowed first, and adding a log entry afterwards. + * + * Includes a call to ChangeTag::canUpdateTags(), so your code doesn't need + * to do that. However, it doesn't check whether the *_id parameters are a + * valid combination. That is up to you to enforce. See ApiTag::execute() for + * an example. + * + * @param array|null $tagsToAdd If none, pass array() or null + * @param array|null $tagsToRemove If none, pass array() or null + * @param int|null $rc_id The rc_id of the change to add the tags to + * @param int|null $rev_id The rev_id of the change to add the tags to + * @param int|null $log_id The log_id of the change to add the tags to + * @param string $params Params to put in the ct_params field of table + * 'change_tag' when adding tags + * @param string $reason Comment for the log + * @param User $user Who to give credit for the action + * @return Status If successful, the value of this Status object will be an + * object (stdClass) with the following fields: + * - logId: the ID of the added log entry, or null if no log entry was added + * (i.e. no operation was performed) + * - addedTags: an array containing the tags that were actually added + * - removedTags: an array containing the tags that were actually removed + * @since 1.25 + */ + public static function updateTagsWithChecks( $tagsToAdd, $tagsToRemove, + $rc_id, $rev_id, $log_id, $params, $reason, User $user ) { + + if ( is_null( $tagsToAdd ) ) { + $tagsToAdd = array(); + } + if ( is_null( $tagsToRemove ) ) { + $tagsToRemove = array(); + } + if ( !$tagsToAdd && !$tagsToRemove ) { + // no-op, don't bother + return Status::newGood( (object)array( + 'logId' => null, + 'addedTags' => array(), + 'removedTags' => array(), + ) ); + } + + // are we allowed to do this? + $result = self::canUpdateTags( $tagsToAdd, $tagsToRemove, $user ); + if ( !$result->isOK() ) { + $result->value = null; + return $result; + } + + // basic rate limiting + if ( $user->pingLimiter( 'changetag' ) ) { + return Status::newFatal( 'actionthrottledtext' ); + } + + // do it! + list( $tagsAdded, $tagsRemoved, $initialTags ) = self::updateTags( $tagsToAdd, + $tagsToRemove, $rc_id, $rev_id, $log_id, $params ); + if ( !$tagsAdded && !$tagsRemoved ) { + // no-op, don't log it + return Status::newGood( (object)array( + 'logId' => null, + 'addedTags' => array(), + 'removedTags' => array(), + ) ); + } + + // log it + $logEntry = new ManualLogEntry( 'tag', 'update' ); + $logEntry->setPerformer( $user ); + $logEntry->setComment( $reason ); + + // find the appropriate target page + if ( $rev_id ) { + $rev = Revision::newFromId( $rev_id ); + if ( $rev ) { + $title = $rev->getTitle(); + $logEntry->setTarget( $rev->getTitle() ); + } + } elseif ( $log_id ) { + // This function is from revision deletion logic and has nothing to do with + // change tags, but it appears to be the only other place in core where we + // perform logged actions on log items. + $logEntry->setTarget( RevDelLogList::suggestTarget( 0, array( $log_id ) ) ); + } + + if ( !$logEntry->getTarget() ) { + // target is required, so we have to set something + $logEntry->setTarget( SpecialPage::getTitleFor( 'Tags' ) ); + } + + $logParams = array( + '4::revid' => $rev_id, + '5::logid' => $log_id, + '6:list:tagsAdded' => $tagsAdded, + '7:number:tagsAddedCount' => count( $tagsAdded ), + '8:list:tagsRemoved' => $tagsRemoved, + '9:number:tagsRemovedCount' => count( $tagsRemoved ), + 'initialTags' => $initialTags, + ); + $logEntry->setParameters( $logParams ); + $logEntry->setRelations( array( 'Tag' => array_merge( $tagsAdded, $tagsRemoved ) ) ); + + $dbw = wfGetDB( DB_MASTER ); + $logId = $logEntry->insert( $dbw ); + // Only send this to UDP, not RC, similar to patrol events + $logEntry->publish( $logId, 'udp' ); + + return Status::newGood( (object)array( + 'logId' => $logId, + 'addedTags' => $tagsAdded, + 'removedTags' => $tagsRemoved, + ) ); + } + /** * Applies all tags-related changes to a query. * Handles selecting tags, and filtering. @@ -265,7 +611,7 @@ class ChangeTags { 'tagfilter', 20, $selected, - array( 'class' => 'mw-tagfilter-input', 'id' => 'tagfilter' ) + array( 'class' => 'mw-tagfilter-input mw-ui-input mw-ui-input-inline', 'id' => 'tagfilter' ) ) ); @@ -289,19 +635,452 @@ class ChangeTags { return $html; } + /** + * Defines a tag in the valid_tag table, without checking that the tag name + * is valid. + * Extensions should NOT use this function; they can use the ListDefinedTags + * hook instead. + * + * @param string $tag Tag to create + * @since 1.25 + */ + public static function defineTag( $tag ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->replace( 'valid_tag', + array( 'vt_tag' ), + array( 'vt_tag' => $tag ), + __METHOD__ ); + + // clear the memcache of defined tags + self::purgeTagCacheAll(); + } + + /** + * Removes a tag from the valid_tag table. The tag may remain in use by + * extensions, and may still show up as 'defined' if an extension is setting + * it from the ListDefinedTags hook. + * + * @param string $tag Tag to remove + * @since 1.25 + */ + public static function undefineTag( $tag ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->delete( 'valid_tag', array( 'vt_tag' => $tag ), __METHOD__ ); + + // clear the memcache of defined tags + self::purgeTagCacheAll(); + } + + /** + * Writes a tag action into the tag management log. + * + * @param string $action + * @param string $tag + * @param string $reason + * @param User $user Who to attribute the action to + * @param int $tagCount For deletion only, how many usages the tag had before + * it was deleted. + * @since 1.25 + */ + protected static function logTagManagementAction( $action, $tag, $reason, + User $user, $tagCount = null ) { + + $dbw = wfGetDB( DB_MASTER ); + + $logEntry = new ManualLogEntry( 'managetags', $action ); + $logEntry->setPerformer( $user ); + // target page is not relevant, but it has to be set, so we just put in + // the title of Special:Tags + $logEntry->setTarget( Title::newFromText( 'Special:Tags' ) ); + $logEntry->setComment( $reason ); + + $params = array( '4::tag' => $tag ); + if ( !is_null( $tagCount ) ) { + $params['5:number:count'] = $tagCount; + } + $logEntry->setParameters( $params ); + $logEntry->setRelations( array( 'Tag' => $tag ) ); + + $logId = $logEntry->insert( $dbw ); + $logEntry->publish( $logId ); + return $logId; + } + + /** + * Is it OK to allow the user to activate this tag? + * + * @param string $tag Tag that you are interested in activating + * @param User|null $user User whose permission you wish to check, or null if + * you don't care (e.g. maintenance scripts) + * @return Status + * @since 1.25 + */ + public static function canActivateTag( $tag, User $user = null ) { + if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) { + return Status::newFatal( 'tags-manage-no-permission' ); + } + + // non-existing tags cannot be activated + $tagUsage = self::tagUsageStatistics(); + if ( !isset( $tagUsage[$tag] ) ) { + return Status::newFatal( 'tags-activate-not-found', $tag ); + } + + // defined tags cannot be activated (a defined tag is either extension- + // defined, in which case the extension chooses whether or not to active it; + // or user-defined, in which case it is considered active) + $definedTags = self::listDefinedTags(); + if ( in_array( $tag, $definedTags ) ) { + return Status::newFatal( 'tags-activate-not-allowed', $tag ); + } + + return Status::newGood(); + } + + /** + * Activates a tag, checking whether it is allowed first, and adding a log + * entry afterwards. + * + * Includes a call to ChangeTag::canActivateTag(), so your code doesn't need + * to do that. + * + * @param string $tag + * @param string $reason + * @param User $user Who to give credit for the action + * @param bool $ignoreWarnings Can be used for API interaction, default false + * @return Status If successful, the Status contains the ID of the added log + * entry as its value + * @since 1.25 + */ + public static function activateTagWithChecks( $tag, $reason, User $user, + $ignoreWarnings = false ) { + + // are we allowed to do this? + $result = self::canActivateTag( $tag, $user ); + if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) { + $result->value = null; + return $result; + } + + // do it! + self::defineTag( $tag ); + + // log it + $logId = self::logTagManagementAction( 'activate', $tag, $reason, $user ); + return Status::newGood( $logId ); + } + + /** + * Is it OK to allow the user to deactivate this tag? + * + * @param string $tag Tag that you are interested in deactivating + * @param User|null $user User whose permission you wish to check, or null if + * you don't care (e.g. maintenance scripts) + * @return Status + * @since 1.25 + */ + public static function canDeactivateTag( $tag, User $user = null ) { + if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) { + return Status::newFatal( 'tags-manage-no-permission' ); + } + + // only explicitly-defined tags can be deactivated + $explicitlyDefinedTags = self::listExplicitlyDefinedTags(); + if ( !in_array( $tag, $explicitlyDefinedTags ) ) { + return Status::newFatal( 'tags-deactivate-not-allowed', $tag ); + } + return Status::newGood(); + } + + /** + * Deactivates a tag, checking whether it is allowed first, and adding a log + * entry afterwards. + * + * Includes a call to ChangeTag::canDeactivateTag(), so your code doesn't need + * to do that. + * + * @param string $tag + * @param string $reason + * @param User $user Who to give credit for the action + * @param bool $ignoreWarnings Can be used for API interaction, default false + * @return Status If successful, the Status contains the ID of the added log + * entry as its value + * @since 1.25 + */ + public static function deactivateTagWithChecks( $tag, $reason, User $user, + $ignoreWarnings = false ) { + + // are we allowed to do this? + $result = self::canDeactivateTag( $tag, $user ); + if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) { + $result->value = null; + return $result; + } + + // do it! + self::undefineTag( $tag ); + + // log it + $logId = self::logTagManagementAction( 'deactivate', $tag, $reason, $user ); + return Status::newGood( $logId ); + } + + /** + * Is it OK to allow the user to create this tag? + * + * @param string $tag Tag that you are interested in creating + * @param User|null $user User whose permission you wish to check, or null if + * you don't care (e.g. maintenance scripts) + * @return Status + * @since 1.25 + */ + public static function canCreateTag( $tag, User $user = null ) { + if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) { + return Status::newFatal( 'tags-manage-no-permission' ); + } + + // no empty tags + if ( $tag === '' ) { + return Status::newFatal( 'tags-create-no-name' ); + } + + // tags cannot contain commas (used as a delimiter in tag_summary table) or + // slashes (would break tag description messages in MediaWiki namespace) + if ( strpos( $tag, ',' ) !== false || strpos( $tag, '/' ) !== false ) { + return Status::newFatal( 'tags-create-invalid-chars' ); + } + + // could the MediaWiki namespace description messages be created? + $title = Title::makeTitleSafe( NS_MEDIAWIKI, "Tag-$tag-description" ); + if ( is_null( $title ) ) { + return Status::newFatal( 'tags-create-invalid-title-chars' ); + } + + // does the tag already exist? + $tagUsage = self::tagUsageStatistics(); + if ( isset( $tagUsage[$tag] ) ) { + return Status::newFatal( 'tags-create-already-exists', $tag ); + } + + // check with hooks + $canCreateResult = Status::newGood(); + Hooks::run( 'ChangeTagCanCreate', array( $tag, $user, &$canCreateResult ) ); + return $canCreateResult; + } + + /** + * Creates a tag by adding a row to the `valid_tag` table. + * + * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to + * do that. + * + * @param string $tag + * @param string $reason + * @param User $user Who to give credit for the action + * @param bool $ignoreWarnings Can be used for API interaction, default false + * @return Status If successful, the Status contains the ID of the added log + * entry as its value + * @since 1.25 + */ + public static function createTagWithChecks( $tag, $reason, User $user, + $ignoreWarnings = false ) { + + // are we allowed to do this? + $result = self::canCreateTag( $tag, $user ); + if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) { + $result->value = null; + return $result; + } + + // do it! + self::defineTag( $tag ); + + // log it + $logId = self::logTagManagementAction( 'create', $tag, $reason, $user ); + return Status::newGood( $logId ); + } + + /** + * Permanently removes all traces of a tag from the DB. Good for removing + * misspelt or temporary tags. + * + * This function should be directly called by maintenance scripts only, never + * by user-facing code. See deleteTagWithChecks() for functionality that can + * safely be exposed to users. + * + * @param string $tag Tag to remove + * @return Status The returned status will be good unless a hook changed it + * @since 1.25 + */ + public static function deleteTagEverywhere( $tag ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin( __METHOD__ ); + + // delete from valid_tag + self::undefineTag( $tag ); + + // find out which revisions use this tag, so we can delete from tag_summary + $result = $dbw->select( 'change_tag', + array( 'ct_rc_id', 'ct_log_id', 'ct_rev_id', 'ct_tag' ), + array( 'ct_tag' => $tag ), + __METHOD__ ); + foreach ( $result as $row ) { + // remove the tag from the relevant row of tag_summary + $tagsToAdd = array(); + $tagsToRemove = array( $tag ); + self::updateTagSummaryRow( $tagsToAdd, $tagsToRemove, $row->ct_rc_id, + $row->ct_rev_id, $row->ct_log_id ); + } + + // delete from change_tag + $dbw->delete( 'change_tag', array( 'ct_tag' => $tag ), __METHOD__ ); + + $dbw->commit( __METHOD__ ); + + // give extensions a chance + $status = Status::newGood(); + Hooks::run( 'ChangeTagAfterDelete', array( $tag, &$status ) ); + // let's not allow error results, as the actual tag deletion succeeded + if ( !$status->isOK() ) { + wfDebug( 'ChangeTagAfterDelete error condition downgraded to warning' ); + $status->ok = true; + } + + // clear the memcache of defined tags + self::purgeTagCacheAll(); + + return $status; + } + + /** + * Is it OK to allow the user to delete this tag? + * + * @param string $tag Tag that you are interested in deleting + * @param User|null $user User whose permission you wish to check, or null if + * you don't care (e.g. maintenance scripts) + * @return Status + * @since 1.25 + */ + public static function canDeleteTag( $tag, User $user = null ) { + $tagUsage = self::tagUsageStatistics(); + + if ( !is_null( $user ) && !$user->isAllowed( 'managechangetags' ) ) { + return Status::newFatal( 'tags-manage-no-permission' ); + } + + if ( !isset( $tagUsage[$tag] ) ) { + return Status::newFatal( 'tags-delete-not-found', $tag ); + } + + if ( $tagUsage[$tag] > self::MAX_DELETE_USES ) { + return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES ); + } + + $extensionDefined = self::listExtensionDefinedTags(); + if ( in_array( $tag, $extensionDefined ) ) { + // extension-defined tags can't be deleted unless the extension + // specifically allows it + $status = Status::newFatal( 'tags-delete-not-allowed' ); + } else { + // user-defined tags are deletable unless otherwise specified + $status = Status::newGood(); + } + + Hooks::run( 'ChangeTagCanDelete', array( $tag, $user, &$status ) ); + return $status; + } + + /** + * Deletes a tag, checking whether it is allowed first, and adding a log entry + * afterwards. + * + * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to + * do that. + * + * @param string $tag + * @param string $reason + * @param User $user Who to give credit for the action + * @param bool $ignoreWarnings Can be used for API interaction, default false + * @return Status If successful, the Status contains the ID of the added log + * entry as its value + * @since 1.25 + */ + public static function deleteTagWithChecks( $tag, $reason, User $user, + $ignoreWarnings = false ) { + + // are we allowed to do this? + $result = self::canDeleteTag( $tag, $user ); + if ( $ignoreWarnings ? !$result->isOK() : !$result->isGood() ) { + $result->value = null; + return $result; + } + + // store the tag usage statistics + $tagUsage = self::tagUsageStatistics(); + + // do it! + $deleteResult = self::deleteTagEverywhere( $tag ); + if ( !$deleteResult->isOK() ) { + return $deleteResult; + } + + // log it + $logId = self::logTagManagementAction( 'delete', $tag, $reason, $user, $tagUsage[$tag] ); + $deleteResult->value = $logId; + return $deleteResult; + } + + /** + * Lists those tags which extensions report as being "active". + * + * @return array + * @since 1.25 + */ + public static function listExtensionActivatedTags() { + // Caching... + global $wgMemc; + $key = wfMemcKey( 'active-tags' ); + $tags = $wgMemc->get( $key ); + if ( $tags ) { + return $tags; + } + + // ask extensions which tags they consider active + $extensionActive = array(); + Hooks::run( 'ChangeTagsListActive', array( &$extensionActive ) ); + + // Short-term caching. + $wgMemc->set( $key, $extensionActive, 300 ); + return $extensionActive; + } + /** * Basically lists defined tags which count even if they aren't applied to anything. - * Tags on items in table 'change_tag' which are not (or no longer) in table 'valid_tag' - * are not included. + * It returns a union of the results of listExplicitlyDefinedTags() and + * listExtensionDefinedTags(). + * + * @return string[] Array of strings: tags + */ + public static function listDefinedTags() { + $tags1 = self::listExplicitlyDefinedTags(); + $tags2 = self::listExtensionDefinedTags(); + return array_values( array_unique( array_merge( $tags1, $tags2 ) ) ); + } + + /** + * Lists tags explicitly defined in the `valid_tag` table of the database. + * Tags in table 'change_tag' which are not in table 'valid_tag' are not + * included. * * Tries memcached first. * * @return string[] Array of strings: tags + * @since 1.25 */ - public static function listDefinedTags() { + public static function listExplicitlyDefinedTags() { // Caching... global $wgMemc; - $key = wfMemcKey( 'valid-tags' ); + $key = wfMemcKey( 'valid-tags-db' ); $tags = $wgMemc->get( $key ); if ( $tags ) { return $tags; @@ -316,8 +1095,33 @@ class ChangeTags { $emptyTags[] = $row->vt_tag; } - wfRunHooks( 'ListDefinedTags', array( &$emptyTags ) ); + $emptyTags = array_filter( array_unique( $emptyTags ) ); + // Short-term caching. + $wgMemc->set( $key, $emptyTags, 300 ); + return $emptyTags; + } + + /** + * Lists tags defined by extensions using the ListDefinedTags hook. + * Extensions need only define those tags they deem to be in active use. + * + * Tries memcached first. + * + * @return string[] Array of strings: tags + * @since 1.25 + */ + public static function listExtensionDefinedTags() { + // Caching... + global $wgMemc; + $key = wfMemcKey( 'valid-tags-hook' ); + $tags = $wgMemc->get( $key ); + if ( $tags ) { + return $tags; + } + + $emptyTags = array(); + Hooks::run( 'ListDefinedTags', array( &$emptyTags ) ); $emptyTags = array_filter( array_unique( $emptyTags ) ); // Short-term caching. @@ -325,13 +1129,46 @@ class ChangeTags { return $emptyTags; } + /** + * Invalidates the short-term cache of defined tags used by the + * list*DefinedTags functions, as well as the tag statistics cache. + * @since 1.25 + */ + public static function purgeTagCacheAll() { + global $wgMemc; + $wgMemc->delete( wfMemcKey( 'active-tags' ) ); + $wgMemc->delete( wfMemcKey( 'valid-tags-db' ) ); + $wgMemc->delete( wfMemcKey( 'valid-tags-hook' ) ); + self::purgeTagUsageCache(); + } + + /** + * Invalidates the tag statistics cache only. + * @since 1.25 + */ + public static function purgeTagUsageCache() { + global $wgMemc; + $wgMemc->delete( wfMemcKey( 'change-tag-statistics' ) ); + } + /** * Returns a map of any tags used on the wiki to number of edits * tagged with them, ordered descending by the hitcount. * + * Keeps a short-term cache in memory, so calling this multiple times in the + * same request should be fine. + * * @return array Array of string => int */ public static function tagUsageStatistics() { + // Caching... + global $wgMemc; + $key = wfMemcKey( 'change-tag-statistics' ); + $stats = $wgMemc->get( $key ); + if ( $stats ) { + return $stats; + } + $out = array(); $dbr = wfGetDB( DB_SLAVE ); @@ -352,6 +1189,26 @@ class ChangeTags { } } + // Cache for a very short time + $wgMemc->set( $key, $out, 300 ); return $out; } + + /** + * Indicate whether change tag editing UI is relevant + * + * Returns true if the user has the necessary right and there are any + * editable tags defined. + * + * This intentionally doesn't check "any addable || any deletable", because + * it seems like it would be more confusing than useful if the checkboxes + * suddenly showed up because some abuse filter stopped defining a tag and + * then suddenly disappeared when someone deleted all uses of that tag. + * + * @param User $user + * @return bool + */ + public static function showTagEditingUI( User $user ) { + return $user->isAllowed( 'changetags' ) && (bool)self::listExplicitlyDefinedTags(); + } } diff --git a/includes/Collation.php b/includes/Collation.php index 1c2c2db3..481d8e70 100644 --- a/includes/Collation.php +++ b/includes/Collation.php @@ -59,7 +59,7 @@ abstract class Collation { # Provide a mechanism for extensions to hook in. $collationObject = null; - wfRunHooks( 'Collation::factory', array( $collationName, &$collationObject ) ); + Hooks::run( 'Collation::factory', array( $collationName, &$collationObject ) ); if ( $collationObject instanceof Collation ) { return $collationObject; @@ -341,7 +341,7 @@ class IcuCollation extends Collation { // Check for CJK $firstChar = mb_substr( $string, 0, 1, 'UTF-8' ); - if ( ord( $firstChar ) > 0x7f && self::isCjk( utf8ToCodepoint( $firstChar ) ) ) { + if ( ord( $firstChar ) > 0x7f && self::isCjk( UtfNormal\Utils::utf8ToCodepoint( $firstChar ) ) ) { return $firstChar; } diff --git a/includes/Cookie.php b/includes/Cookie.php deleted file mode 100644 index cb041904..00000000 --- a/includes/Cookie.php +++ /dev/null @@ -1,291 +0,0 @@ -name = $name; - $this->set( $value, $attr ); - } - - /** - * Sets a cookie. Used before a request to set up any individual - * cookies. Used internally after a request to parse the - * Set-Cookie headers. - * - * @param string $value The value of the cookie - * @param array $attr Possible key/values: - * expires A date string - * path The path this cookie is used on - * domain Domain this cookie is used on - * @throws MWException - */ - public function set( $value, $attr ) { - $this->value = $value; - - if ( isset( $attr['expires'] ) ) { - $this->isSessionKey = false; - $this->expires = strtotime( $attr['expires'] ); - } - - if ( isset( $attr['path'] ) ) { - $this->path = $attr['path']; - } else { - $this->path = '/'; - } - - if ( isset( $attr['domain'] ) ) { - if ( self::validateCookieDomain( $attr['domain'] ) ) { - $this->domain = $attr['domain']; - } - } else { - throw new MWException( 'You must specify a domain.' ); - } - } - - /** - * Return the true if the cookie is valid is valid. Otherwise, - * false. The uses a method similar to IE cookie security - * described here: - * http://kuza55.blogspot.com/2008/02/understanding-cookie-security.html - * A better method might be to use a blacklist like - * http://publicsuffix.org/ - * - * @todo fixme fails to detect 3-letter top-level domains - * @todo fixme fails to detect 2-letter top-level domains for single-domain use (probably - * not a big problem in practice, but there are test cases) - * - * @param string $domain The domain to validate - * @param string $originDomain (optional) the domain the cookie originates from - * @return bool - */ - public static function validateCookieDomain( $domain, $originDomain = null ) { - $dc = explode( ".", $domain ); - - // Don't allow a trailing dot or addresses without a or just a leading dot - if ( substr( $domain, -1 ) == '.' || - count( $dc ) <= 1 || - count( $dc ) == 2 && $dc[0] === '' - ) { - return false; - } - - // Only allow full, valid IP addresses - if ( preg_match( '/^[0-9.]+$/', $domain ) ) { - if ( count( $dc ) != 4 ) { - return false; - } - - if ( ip2long( $domain ) === false ) { - return false; - } - - if ( $originDomain == null || $originDomain == $domain ) { - return true; - } - - } - - // Don't allow cookies for "co.uk" or "gov.uk", etc, but allow "supermarket.uk" - if ( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) { - if ( ( count( $dc ) == 2 && strlen( $dc[0] ) <= 2 ) - || ( count( $dc ) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) { - return false; - } - if ( ( count( $dc ) == 2 || ( count( $dc ) == 3 && $dc[0] == '' ) ) - && preg_match( '/(com|net|org|gov|edu)\...$/', $domain ) ) { - return false; - } - } - - if ( $originDomain != null ) { - if ( substr( $domain, 0, 1 ) != '.' && $domain != $originDomain ) { - return false; - } - - if ( substr( $domain, 0, 1 ) == '.' - && substr_compare( - $originDomain, - $domain, - -strlen( $domain ), - strlen( $domain ), - true - ) != 0 - ) { - return false; - } - } - - return true; - } - - /** - * Serialize the cookie jar into a format useful for HTTP Request headers. - * - * @param string $path The path that will be used. Required. - * @param string $domain The domain that will be used. Required. - * @return string - */ - public function serializeToHttpRequest( $path, $domain ) { - $ret = ''; - - if ( $this->canServeDomain( $domain ) - && $this->canServePath( $path ) - && $this->isUnExpired() ) { - $ret = $this->name . '=' . $this->value; - } - - return $ret; - } - - /** - * @param string $domain - * @return bool - */ - protected function canServeDomain( $domain ) { - if ( $domain == $this->domain - || ( strlen( $domain ) > strlen( $this->domain ) - && substr( $this->domain, 0, 1 ) == '.' - && substr_compare( - $domain, - $this->domain, - -strlen( $this->domain ), - strlen( $this->domain ), - true - ) == 0 - ) - ) { - return true; - } - - return false; - } - - /** - * @param string $path - * @return bool - */ - protected function canServePath( $path ) { - return ( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 ); - } - - /** - * @return bool - */ - protected function isUnExpired() { - return $this->isSessionKey || $this->expires > time(); - } -} - -class CookieJar { - private $cookie = array(); - - /** - * Set a cookie in the cookie jar. Make sure only one cookie per-name exists. - * @see Cookie::set() - * @param string $name - * @param string $value - * @param array $attr - */ - public function setCookie( $name, $value, $attr ) { - /* cookies: case insensitive, so this should work. - * We'll still send the cookies back in the same case we got them, though. - */ - $index = strtoupper( $name ); - - if ( isset( $this->cookie[$index] ) ) { - $this->cookie[$index]->set( $value, $attr ); - } else { - $this->cookie[$index] = new Cookie( $name, $value, $attr ); - } - } - - /** - * @see Cookie::serializeToHttpRequest - * @param string $path - * @param string $domain - * @return string - */ - public function serializeToHttpRequest( $path, $domain ) { - $cookies = array(); - - foreach ( $this->cookie as $c ) { - $serialized = $c->serializeToHttpRequest( $path, $domain ); - - if ( $serialized ) { - $cookies[] = $serialized; - } - } - - return implode( '; ', $cookies ); - } - - /** - * Parse the content of an Set-Cookie HTTP Response header. - * - * @param string $cookie - * @param string $domain Cookie's domain - * @return null - */ - public function parseCookieResponseHeader( $cookie, $domain ) { - $len = strlen( 'Set-Cookie:' ); - - if ( substr_compare( 'Set-Cookie:', $cookie, 0, $len, true ) === 0 ) { - $cookie = substr( $cookie, $len ); - } - - $bit = array_map( 'trim', explode( ';', $cookie ) ); - - if ( count( $bit ) >= 1 ) { - list( $name, $value ) = explode( '=', array_shift( $bit ), 2 ); - $attr = array(); - - foreach ( $bit as $piece ) { - $parts = explode( '=', $piece ); - if ( count( $parts ) > 1 ) { - $attr[strtolower( $parts[0] )] = $parts[1]; - } else { - $attr[strtolower( $parts[0] )] = true; - } - } - - if ( !isset( $attr['domain'] ) ) { - $attr['domain'] = $domain; - } elseif ( !Cookie::validateCookieDomain( $attr['domain'], $domain ) ) { - return null; - } - - $this->setCookie( $name, $value, $attr ); - } - } -} diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index aad42aac..010c471c 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -52,6 +52,8 @@ if ( !defined( 'MEDIAWIKI' ) ) { die( 1 ); } +/** @endcond */ + /** * wgConf hold the site configuration. * Not used for much in a default install. @@ -71,11 +73,9 @@ $wgConfigRegistry = array( /** * MediaWiki version number - * Note that MediaWikiVersionFetcher::fetchVersion() uses a regex to check this. - * Using single quotes is, therefore, important here. * @since 1.2 */ -$wgVersion = '1.24.2'; +$wgVersion = '1.25.1'; /** * Name of the site. It must be changed in LocalSettings.php @@ -154,12 +154,15 @@ $wgUsePathInfo = ( strpos( PHP_SAPI, 'cgi' ) === false ) && ( strpos( PHP_SAPI, 'isapi' ) === false ); /** - * The extension to append to script names by default. This can either be .php - * or .php5. + * The extension to append to script names by default. + * + * Some hosting providers used PHP 4 for *.php files, and PHP 5 for *.php5. + * This variable was provided to support those providers. * - * Some hosting providers use PHP 4 for *.php files, and PHP 5 for *.php5. This - * variable is provided to support those providers. * @since 1.11 + * @deprecated since 1.25; support for '.php5' is being phased out of MediaWiki + * proper. Backward-compatibility can be maintained by configuring your web + * server to rewrite URLs. See RELEASE-NOTES for details. */ $wgScriptExtension = '.php'; @@ -220,12 +223,19 @@ $wgLocalStylePath = false; */ $wgExtensionAssetsPath = false; +/** + * Filesystem extensions directory. + * Defaults to "{$IP}/extensions". + * @since 1.25 + */ +$wgExtensionDirectory = "{$IP}/extensions"; + /** * Filesystem stylesheets directory. * Defaults to "{$IP}/skins". * @since 1.3 */ -$wgStyleDirectory = false; +$wgStyleDirectory = "{$IP}/skins"; /** * The URL path for primary article page views. This path should contain $1, @@ -259,6 +269,23 @@ $wgFileCacheDirectory = false; */ $wgLogo = false; +/** + * Array with URL paths to HD versions of the wiki logo. The scaled logo size + * should be under 135x155 pixels. + * Only 1.5x and 2x versions are supported. + * + * @par Example: + * @code + * $wgLogoHD = array( + * "1.5x" => "path/to/1.5x_version.png", + * "2x" => "path/to/2x_version.png" + * ); + * @endcode + * + * @since 1.25 + */ +$wgLogoHD = false; + /** * The URL path of the shortcut icon. * @since 1.6 @@ -272,6 +299,16 @@ $wgFavicon = '/favicon.ico'; */ $wgAppleTouchIcon = false; +/** + * Value for the referrer policy meta tag. + * One of 'never', 'default', 'origin', 'always'. Setting it to false just + * prevents the meta tag from being output. + * See http://www.w3.org/TR/referrer-policy/ for details. + * + * @since 1.25 + */ +$wgReferrerPolicy = false; + /** * The local filesystem path to a temporary directory. This is not required to * be web accessible. @@ -943,12 +980,13 @@ $wgExiv2Command = '/usr/bin/exiv2'; * are passed as parameters after $srcPath, $dstPath, $width, $height */ $wgSVGConverters = array( - 'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output', + 'ImageMagick' => + '$path/convert -background "#ffffff00" -thumbnail $widthx$height\! $input PNG:$output', 'sodipodi' => '$path/sodipodi -z -w $width -f $input -e $output', 'inkscape' => '$path/inkscape -z -w $width -f $input -e $output', 'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d ' . '$output $input', - 'rsvg' => '$path/rsvg -w $width -h $height $input $output', + 'rsvg' => '$path/rsvg-convert -w $width -h $height -o $output $input', 'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output', 'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ), ); @@ -1244,6 +1282,46 @@ $wgThumbnailBuckets = null; */ $wgThumbnailMinimumBucketDistance = 50; +/** + * When defined, is an array of thumbnail widths to be rendered at upload time. The idea is to + * prerender common thumbnail sizes, in order to avoid the necessity to render them on demand, which + * has a performance impact for the first client to view a certain size. + * + * This obviously means that more disk space is needed per upload upfront. + * + * @since 1.25 + */ + +$wgUploadThumbnailRenderMap = array(); + +/** + * The method through which the thumbnails will be prerendered for the entries in + * $wgUploadThumbnailRenderMap + * + * The method can be either "http" or "jobqueue". The former uses an http request to hit the + * thumbnail's URL. + * This method only works if thumbnails are configured to be rendered by a 404 handler. The latter + * option uses the job queue to render the thumbnail. + * + * @since 1.25 + */ +$wgUploadThumbnailRenderMethod = 'jobqueue'; + +/** + * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom Host HTTP header. + * + * @since 1.25 + */ +$wgUploadThumbnailRenderHttpCustomHost = false; + +/** + * When using the "http" wgUploadThumbnailRenderMethod, lets one specify a custom domain to send the + * HTTP request to. + * + * @since 1.25 + */ +$wgUploadThumbnailRenderHttpCustomDomain = false; + /** * Default parameters for the "" tag */ @@ -1272,9 +1350,11 @@ $wgDirectoryMode = 0777; * Generate and use thumbnails suitable for screens with 1.5 and 2.0 pixel densities. * * This means a 320x240 use of an image on the wiki will also generate 480x360 and 640x480 - * thumbnails, output via data-src-1-5 and data-src-2-0. Runtime JavaScript switches the - * images in after loading the original low-resolution versions depending on the reported - * window.devicePixelRatio. + * thumbnails, output via the srcset attribute. + * + * On older browsers, a JavaScript polyfill switches the appropriate images in after loading + * the original low-resolution versions depending on the reported window.devicePixelRatio. + * The polyfill can be found in the jquery.hidpi module. */ $wgResponsiveImages = true; @@ -1345,7 +1425,7 @@ $wgDjvuOutputExtension = 'jpg'; /** * Site admin email address. * - * Defaults to "wikiadmin@{$wgServerName}". + * Defaults to "wikiadmin@$wgServerName". */ $wgEmergencyContact = false; @@ -1354,7 +1434,7 @@ $wgEmergencyContact = false; * * The address we should use as sender when a user is requesting his password. * - * Defaults to "apache@{$wgServerName}". + * Defaults to "apache@$wgServerName". */ $wgPasswordSender = false; @@ -1664,6 +1744,9 @@ $wgAllDBsAreLocalhost = false; * $wgSharedPrefix is the table prefix for the shared database. It defaults to * $wgDBprefix. * + * $wgSharedSchema is the table schema for the shared database. It defaults to + * $wgDBmwschema. + * * @deprecated since 1.21 In new code, use the $wiki parameter to wfGetLB() to * access remote databases. Using wfGetLB() allows the shared database to * reside on separate servers to the wiki's own database, with suitable @@ -1681,6 +1764,12 @@ $wgSharedPrefix = false; */ $wgSharedTables = array( 'user', 'user_properties' ); +/** + * @see $wgSharedDB + * @since 1.23 + */ +$wgSharedSchema = false; + /** * Database load balancer * This is a two-dimensional array, an array of server info structures @@ -1959,15 +2048,6 @@ $wgAllowSlowParserFunctions = false; */ $wgAllowSchemaUpdates = true; -/** - * Anti-lock flags - bitfield - * - ALF_NO_LINK_LOCK: - * Don't use locking reads when updating the link table. This is - * necessary for wikis with a high edit rate for performance - * reasons, but may cause link table inconsistency - */ -$wgAntiLockFlags = 0; - /** * Maximum article size in kilobytes */ @@ -2063,42 +2143,20 @@ $wgLanguageConverterCacheType = CACHE_ANYTHING; */ $wgObjectCaches = array( CACHE_NONE => array( 'class' => 'EmptyBagOStuff' ), - CACHE_DB => array( 'class' => 'SqlBagOStuff', 'table' => 'objectcache' ), + CACHE_DB => array( 'class' => 'SqlBagOStuff', 'loggroup' => 'SQLBagOStuff' ), CACHE_ANYTHING => array( 'factory' => 'ObjectCache::newAnything' ), CACHE_ACCEL => array( 'factory' => 'ObjectCache::newAccelerator' ), - CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached' ), + CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached', 'loggroup' => 'memcached' ), 'apc' => array( 'class' => 'APCBagOStuff' ), 'xcache' => array( 'class' => 'XCacheBagOStuff' ), 'wincache' => array( 'class' => 'WinCacheBagOStuff' ), - 'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff' ), - 'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff' ), + 'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff', 'loggroup' => 'memcached' ), + 'memcached-pecl' => array( 'class' => 'MemcachedPeclBagOStuff', 'loggroup' => 'memcached' ), 'hash' => array( 'class' => 'HashBagOStuff' ), ); -/** - * Map of bloom filter store names to configuration arrays. - * - * Example: - * $wgBloomFilterStores['main'] = array( - * 'cacheId' => 'main-v1', - * 'class' => 'BloomCacheRedis', - * 'redisServers' => array( '127.0.0.1:6379' ), - * 'redisConfig' => array( 'connectTimeout' => 2 ) - * ); - * - * A primary bloom filter must be created manually. - * Example in eval.php: - * - * BloomCache::get( 'main' )->init( 'shared', 1000000000, .001 ); - * - * The size should be as large as practical given wiki size and resources. - * - * @since 1.24 - */ -$wgBloomFilterStores = array(); - /** * The expiry time for the parser cache, in seconds. * The default is 86400 (one day). @@ -2310,6 +2368,23 @@ $wgClockSkewFudge = 5; */ $wgInvalidateCacheOnLocalSettingsChange = true; +/** + * When loading extensions through the extension registration system, this + * can be used to invalidate the cache. A good idea would be to set this to + * one file, you can just `touch` that one to invalidate the cache + * + * @par Example: + * @code + * $wgExtensionInfoMtime = filemtime( "$IP/LocalSettings.php" ); + * @endcode + * + * If set to false, the mtime for each individual JSON file will be checked, + * which can be slow if a large number of extensions are being loaded. + * + * @var int|bool + */ +$wgExtensionInfoMTime = false; + /** @} */ # end of cache settings /************************************************************************//** @@ -2686,8 +2761,8 @@ $wgBrowserBlackList = array( $wgLegacySchemaConversion = false; /** - * Enable dates like 'May 12' instead of '12 May', this only takes effect if - * the interface is set to English. + * Enable dates like 'May 12' instead of '12 May', if the default date format + * is 'dmy or mdy'. */ $wgAmericanDates = false; @@ -3050,6 +3125,7 @@ $wgEditPageFrameOptions = 'DENY'; * - 'DENY': Do not allow framing. This is recommended for most wikis. * - 'SAMEORIGIN': Allow framing by pages on the same domain. * - false: Allow all framing. + * Note: $wgBreakFrames will override this for human formatted API output. */ $wgApiFrameOptions = 'DENY'; @@ -3088,6 +3164,7 @@ $wgExperimentalHtmlIds = false; * for the icon, the following keys are used: * - src: An absolute url to the image to use for the icon, this is recommended * but not required, however some skins will ignore icons without an image + * - srcset: optional additional-resolution images; see HTML5 specs * - url: The url to use in the a element around the text or icon, if not set an a element will * not be outputted * - alt: This is the text form of the icon, it will be displayed without an image in @@ -3104,7 +3181,9 @@ $wgFooterIcons = array( ), "poweredby" => array( "mediawiki" => array( - // src defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png" + // Defaults to point at + // "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png" + // plus srcset for 1.5x, 2x resolution variants. "src" => null, "url" => "//www.mediawiki.org/", "alt" => "Powered by MediaWiki", @@ -3161,6 +3240,8 @@ $wgEnableCanonicalServerLink = false; * . Without this, an attacker can send their own * cross-domain policy unless it is prevented by the crossdomain.xml file at * the domain root. + * + * @since 1.25 */ $wgMangleFlashPolicy = true; @@ -3239,8 +3320,8 @@ $wgResourceModules = array(); * ), * ); * // Note the '+' character: - * $wgResourceModuleSkinStyles['+foo'] = array( - * 'bar' => 'skins/Foo/bar.css', + * $wgResourceModuleSkinStyles['foo'] = array( + * '+bar' => 'skins/Foo/bar.css', * ); * @endcode * @@ -3267,8 +3348,6 @@ $wgResourceModules = array(); * * As with $wgResourceModules, paths default to being relative to the MediaWiki root. * You should always provide a localBasePath and remoteBasePath (or remoteExtPath/remoteSkinPath). - * Either for all skin styles at once (first example below) or for each module separately (second - * example). * * @par Example: * @code @@ -3278,19 +3357,6 @@ $wgResourceModules = array(); * 'remoteSkinPath' => 'Foo', * 'localBasePath' => __DIR__, * ); - * - * $wgResourceModuleSkinStyles['foo'] = array( - * 'bar' => array( - * 'bar.css', - * 'remoteSkinPath' => 'Foo', - * 'localBasePath' => __DIR__, - * ), - * 'quux' => array( - * 'quux.css', - * 'remoteSkinPath' => 'Foo', - * 'localBasePath' => __DIR__, - * ), - * ); * @endcode */ $wgResourceModuleSkinStyles = array(); @@ -3373,15 +3439,6 @@ $wgResourceLoaderMinifierMaxLineLength = 1000; */ $wgIncludeLegacyJavaScript = true; -/** - * Whether to include the jQuery Migrate library, which lets legacy JS that - * requires jQuery 1.8.x to work and breaks with 1.9.x+. - * - * @since 1.24 - * @deprecated since 1.24, to be removed in 1.25 - */ -$wgIncludejQueryMigrate = false; - /** * Whether to preload the mediawiki.util module as blocking module in the top * queue. @@ -3480,6 +3537,9 @@ $wgResourceLoaderExperimentalAsyncLoading = false; * * Changes to LESS variables do not trigger cache invalidation. * + * If the LESS variables need to be dynamic, you can use the + * ResourceLoaderGetLessVars hook (since 1.25). + * * @par Example: * @code * $wgResourceLoaderLESSVars = array( @@ -3714,6 +3774,18 @@ $wgInterwikiFallbackSite = 'wiki'; /** @} */ # end of Interwiki caching settings. +/** + * @name SiteStore caching settings. + * @{ + */ + +/** + * Specify the file location for the Sites json cache file. + */ +$wgSitesCacheFile = false; + +/** @} */ # end of SiteStore caching settings. + /** * If local interwikis are set up which allow redirects, * set this regexp to restrict URLs which will be displayed @@ -3785,19 +3857,12 @@ $wgNamespacesWithSubpages = array( * A message with the suffix '-desc' should be added as a description message * to have extra information on Special:TrackingCategories. * + * @deprecated since 1.25 Extensions should now register tracking categories using + * the new extension registration system. + * * @since 1.23 */ -$wgTrackingCategories = array( - 'index-category', - 'noindex-category', - 'expensive-parserfunction-category', - 'post-expand-template-argument-category', - 'post-expand-template-inclusion-category', - 'hidden-category-category', - 'broken-file-category', - 'node-count-exceeded-category', - 'expansion-depth-exceeded-category', -); +$wgTrackingCategories = array(); /** * Array of namespaces which can be deemed to contain valid "content", as far @@ -3913,7 +3978,7 @@ $wgUrlProtocols = array( ); /** - * If true, removes (substitutes) templates in "~~~~" signatures. + * If true, removes (by substituting) templates in signatures. */ $wgCleanSignatures = true; @@ -4108,15 +4173,6 @@ $wgTranscludeCacheExpiry = 3600; */ $wgArticleCountMethod = 'link'; -/** - * wgHitcounterUpdateFreq sets how often page counters should be updated, higher - * values are easier on the database. A value of 1 causes the counters to be - * updated on every hit, any higher value n cause them to update *on average* - * every n hits. Should be set to either 1 or something largish, eg 1000, for - * maximum efficiency. - */ -$wgHitcounterUpdateFreq = 1; - /** * How many days user must be idle before he is considered inactive. Will affect * the number shown on Special:Statistics, Special:ActiveUsers, and the @@ -4279,7 +4335,7 @@ $wgDefaultUserOptions = array( 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, - 'extendwatchlist' => 0, + 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'gender' => 'unknown', @@ -4305,7 +4361,7 @@ $wgDefaultUserOptions = array( 'thumbsize' => 5, 'underline' => 2, 'uselivepreview' => 0, - 'usenewrc' => 0, + 'usenewrc' => 1, 'watchcreations' => 1, 'watchdefault' => 1, 'watchdeletion' => 0, @@ -4517,6 +4573,8 @@ $wgGroupPermissions['user']['reupload-shared'] = true; $wgGroupPermissions['user']['minoredit'] = true; $wgGroupPermissions['user']['purge'] = true; // can use ?action=purge without clicking "ok" $wgGroupPermissions['user']['sendemail'] = true; +$wgGroupPermissions['user']['applychangetags'] = true; +$wgGroupPermissions['user']['changetags'] = true; // Implicit group for accounts that pass $wgAutoConfirmAge $wgGroupPermissions['autoconfirmed']['autoconfirmed'] = true; @@ -4577,6 +4635,7 @@ $wgGroupPermissions['sysop']['suppressredirect'] = true; #$wgGroupPermissions['sysop']['pagelang'] = true; #$wgGroupPermissions['sysop']['upload_by_url'] = true; $wgGroupPermissions['sysop']['mergehistory'] = true; +$wgGroupPermissions['sysop']['managechangetags'] = true; // Permission to change users' group assignments $wgGroupPermissions['bureaucrat']['userrights'] = true; @@ -4793,7 +4852,6 @@ $wgAutopromote = array( * @endcode * Where event is either: * - 'onEdit' (when user edits) - * - 'onView' (when user views the wiki) * * Criteria has the same format as $wgAutopromote * @@ -4802,7 +4860,6 @@ $wgAutopromote = array( */ $wgAutopromoteOnce = array( 'onEdit' => array(), - 'onView' => array() ); /** @@ -4992,6 +5049,17 @@ $wgRateLimits = array( 'ip' => null, 'subnet' => null, ), + 'stashedit' => array( // stashing edits into cache before save + 'anon' => null, + 'user' => null, + 'newbie' => null, + 'ip' => null, + 'subnet' => null, + ), + 'changetag' => array( // adding or removing change tags + 'user' => null, + 'newbie' => null, + ), ); /** @@ -5209,9 +5277,11 @@ $wgDebugDumpSqlLength = 500; * Log destinations may be one of the following: * - false to completely remove from the output, including from $wgDebugLogFile. * - string values specifying a filename or URI. - * - associative array mapping 'destination' key to the desired filename or URI. - * The associative array may also contain a 'sample' key with an integer value, - * specifying a sampling factor. + * - associative array with keys: + * - 'destination' desired filename or URI. + * - 'sample' an integer value, specifying a sampling factor (optional) + * - 'level' A \Psr\Log\LogLevel constant, indicating the minimum level + * to log (optional, since 1.25) * * @par Example: * @code @@ -5220,14 +5290,40 @@ $wgDebugDumpSqlLength = 500; * * @par Advanced example: * @code - * $wgDebugLogGroups['memcached'] = ( + * $wgDebugLogGroups['memcached'] = array( * 'destination' => '/var/log/mediawiki/memcached.log', * 'sample' => 1000, // log 1 message out of every 1,000. + * 'level' => \Psr\Log\LogLevel::WARNING * ); * @endcode */ $wgDebugLogGroups = array(); +/** + * Default service provider for creating Psr\Log\LoggerInterface instances. + * + * The value should be an array suitable for use with + * ObjectFactory::getObjectFromSpec(). The created object is expected to + * implement the MediaWiki\Logger\Spi interface. See ObjectFactory for additional + * details. + * + * Alternately the MediaWiki\Logger\LoggerFactory::registerProvider method can + * be called to inject an MediaWiki\Logger\Spi instance into the LoggerFactory + * and bypass the use of this configuration variable entirely. + * + * @par To completely disable logging: + * @code + * $wgMWLoggerDefaultSpi = array( 'class' => '\\MediaWiki\\Logger\\NullSpi' ); + * @endcode + * + * @since 1.25 + * @var array $wgMWLoggerDefaultSpi + * @see MwLogger + */ +$wgMWLoggerDefaultSpi = array( + 'class' => '\\MediaWiki\\Logger\\LegacySpi', +); + /** * Display debug data at the bottom of the main content area. * @@ -5308,6 +5404,7 @@ $wgDeprecationReleaseLimit = false; /** * Only record profiling info for pages that took longer than this + * @deprecated since 1.25: set $wgProfiler['threshold'] instead. */ $wgProfileLimit = 0.0; @@ -5326,8 +5423,10 @@ $wgProfileCallTree = false; /** * Should application server host be put into profiling table + * + * @deprecated set $wgProfiler['perhost'] = true instead */ -$wgProfilePerHost = false; +$wgProfilePerHost = null; /** * Host for UDP profiler. @@ -5335,14 +5434,18 @@ $wgProfilePerHost = false; * The host should be running a daemon which can be obtained from MediaWiki * Git at: * http://git.wikimedia.org/tree/operations%2Fsoftware.git/master/udpprofile + * + * @deprecated set $wgProfiler['udphost'] instead */ -$wgUDPProfilerHost = '127.0.0.1'; +$wgUDPProfilerHost = null; /** * Port for UDP profiler. * @see $wgUDPProfilerHost + * + * @deprecated set $wgProfiler['udpport'] instead */ -$wgUDPProfilerPort = '3811'; +$wgUDPProfilerPort = null; /** * Format string for the UDP profiler. The UDP profiler invokes sprintf() with @@ -5352,13 +5455,10 @@ $wgUDPProfilerPort = '3811'; * * @see $wgStatsFormatString * @since 1.22 + * + * @deprecated set $wgProfiler['udpformat'] instead */ -$wgUDPProfilerFormatString = "%s - %d %f %f %f %f %s\n"; - -/** - * Output debug message on every wfProfileIn/wfProfileOut - */ -$wgDebugFunctionEntry = false; +$wgUDPProfilerFormatString = null; /** * Destination for wfIncrStats() data... @@ -5389,12 +5489,6 @@ $wgAggregateStatsID = false; */ $wgStatsFormatString = "stats/%s - %s 1 1 1 1 %s\n"; -/** - * Whereas to count the number of time an article is viewed. - * Does not work if pages are cached (for example with squid). - */ -$wgDisableCounters = false; - /** * InfoAction retrieves a list of transclusion links (both to and from). * This number puts a limit on that query in the case of highly transcluded @@ -5429,25 +5523,6 @@ $wgParserTestFiles = array( */ $wgEnableJavaScriptTest = false; -/** - * Configuration for javascript testing. - */ -$wgJavaScriptTestConfig = array( - 'qunit' => array( - // Page where documentation can be found relevant to the QUnit test suite being ran. - // Used in the intro paragraph on [[Special:JavaScriptTest/qunit]] for the - // documentation link in the "javascripttest-qunit-intro" message. - 'documentation' => '//www.mediawiki.org/wiki/Manual:JavaScript_unit_testing', - // If you are submitting the QUnit test suite to a TestSwarm instance, - // point this to the "inject.js" script of that instance. This is was registers - // the QUnit hooks to extract the test results and push them back up into the - // TestSwarm database. - // @example 'http://localhost/testswarm/js/inject.js' - // @example '//integration.mediawiki.org/testswarm/js/inject.js' - 'testswarm-injectjs' => false, - ), -); - /** * Overwrite the caching key prefix with custom value. * @since 1.19 @@ -5494,9 +5569,24 @@ $wgSearchHighlightBoundaries = '[\p{Z}\p{P}\p{C}]'; * PHP wrapper to avoid firing up mediawiki for every keystroke * * Placeholders: {searchTerms} + * + * @deprecated since 1.25 Use $wgOpenSearchTemplates['application/x-suggestions+json'] instead */ $wgOpenSearchTemplate = false; +/** + * Templates for OpenSearch suggestions, defaults to API action=opensearch + * + * Sites with heavy load would typically have these point to a custom + * PHP wrapper to avoid firing up mediawiki for every keystroke + * + * Placeholders: {searchTerms} + */ +$wgOpenSearchTemplates = array( + 'application/x-suggestions+json' => false, + 'application/x-suggestions+xml' => false, +); + /** * Enable OpenSearch suggestions requested by MediaWiki. Set this to * false if you've disabled scripts that use api?action=opensearch and @@ -5511,6 +5601,11 @@ $wgEnableOpenSearchSuggest = true; */ $wgOpenSearchDefaultLimit = 10; +/** + * Minimum length of extract in . Actual extracts will last until the end of sentence. + */ +$wgOpenSearchDescriptionLength = 100; + /** * Expiry time for search suggestion responses */ @@ -5722,9 +5817,9 @@ $wgGitRepositoryViewers = array( /** * Recentchanges items are periodically purged; entries older than this many * seconds will go. - * Default: 13 weeks = about three months + * Default: 90 days = about three months */ -$wgRCMaxAge = 13 * 7 * 24 * 3600; +$wgRCMaxAge = 90 * 24 * 3600; /** * Filter $wgRCLinkDays by $wgRCMaxAge to avoid showing links for numbers @@ -5876,11 +5971,6 @@ $wgAdvertisedFeedTypes = array( 'atom' ); */ $wgRCShowWatchingUsers = false; # UPO -/** - * Show watching users in Page views - */ -$wgPageShowWatchingUsers = false; - /** * Show the amount of changed characters in recent changes */ @@ -6160,6 +6250,8 @@ $wgExtensionMessagesFiles = array(); * en.json, de.json, etc. Extensions with messages in multiple places may specify an array of * message directories. * + * Message directories in core should be added to LocalisationCache::getMessagesDirs() + * * @par Simple example: * @code * $wgMessagesDirs['Example'] = __DIR__ . '/i18n'; @@ -6175,10 +6267,7 @@ $wgExtensionMessagesFiles = array(); * @endcode * @since 1.23 */ -$wgMessagesDirs = array( - 'core' => "$IP/languages/i18n", - 'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n", -); +$wgMessagesDirs = array(); /** * Array of files with list(s) of extension entry points to be used in @@ -6254,7 +6343,7 @@ $wgAutoloadAttemptLowercase = true; * 'version' => '1.9.0', * 'url' => 'http://example.org/example-extension/', * 'descriptionmsg' => 'exampleextension-desc', - * 'license-name' => 'GPL-2.0', + * 'license-name' => 'GPL-2.0+', * ); * @endcode * @@ -6288,7 +6377,7 @@ $wgAutoloadAttemptLowercase = true; * localizable message (omit in favour of 'descriptionmsg'). * * - license-name: Short name of the license (used as label for the link), such - * as "GPL-2.0" or "MIT" (https://spdx.org/licenses/ for a list of identifiers). + * as "GPL-2.0+" or "MIT" (https://spdx.org/licenses/ for a list of identifiers). */ $wgExtensionCredits = array(); @@ -6340,7 +6429,6 @@ $wgHooks = array(); */ $wgJobClasses = array( 'refreshLinks' => 'RefreshLinksJob', - 'refreshLinks2' => 'RefreshLinksJob2', // b/c 'htmlCacheUpdate' => 'HTMLCacheUpdateJob', 'sendMail' => 'EmaillingJob', 'enotifNotify' => 'EnotifNotifyJob', @@ -6348,6 +6436,10 @@ $wgJobClasses = array( 'uploadFromUrl' => 'UploadFromUrlJob', 'AssembleUploadChunks' => 'AssembleUploadChunksJob', 'PublishStashedFile' => 'PublishStashedFileJob', + 'ThumbnailRender' => 'ThumbnailRenderJob', + 'recentChangesUpdate' => 'RecentChangesUpdateJob', + 'refreshLinksPrioritized' => 'RefreshLinksJob', // for cascading protection + 'enqueue' => 'EnqueueJob', // local queue for multi-DC setups 'null' => 'NullJob' ); @@ -6390,7 +6482,7 @@ $wgJobTypeConf = array( * These settings should be global to all wikis. */ $wgJobQueueAggregator = array( - 'class' => 'JobQueueAggregatorMemc' + 'class' => 'JobQueueAggregatorNull' ); /** @@ -6398,8 +6490,7 @@ $wgJobQueueAggregator = array( * Expensive Querypages are already updated. */ $wgSpecialPageCacheUpdates = array( - 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' ), - 'Activeusers' => array( 'SpecialActiveUsers', 'cacheUpdate' ), + 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' ) ); /** @@ -6497,6 +6588,8 @@ $wgLogTypes = array( 'patrol', 'merge', 'suppress', + 'tag', + 'managetags', ); /** @@ -6533,7 +6626,8 @@ $wgLogRestrictions = array( * for the link text. */ $wgFilterLogTypes = array( - 'patrol' => true + 'patrol' => true, + 'tag' => true, ); /** @@ -6589,22 +6683,14 @@ $wgLogHeaders = array( * Extensions with custom log types may add to this array. */ $wgLogActions = array( - 'block/block' => 'blocklogentry', - 'block/unblock' => 'unblocklogentry', - 'block/reblock' => 'reblock-logentry', 'protect/protect' => 'protectedarticle', 'protect/modify' => 'modifiedarticleprotection', 'protect/unprotect' => 'unprotectedarticle', 'protect/move_prot' => 'movedarticleprotection', - 'import/upload' => 'import-logentry-upload', - 'import/interwiki' => 'import-logentry-interwiki', - 'merge/merge' => 'pagemerge-logentry', - 'suppress/block' => 'blocklogentry', - 'suppress/reblock' => 'reblock-logentry', ); /** - * The same as above, but here values are names of functions, + * The same as above, but here values are names of classes, * not messages. * @see LogPage::actionText * @see LogFormatter @@ -6622,9 +6708,22 @@ $wgLogActionsHandlers = array( 'patrol/patrol' => 'PatrolLogFormatter', 'rights/rights' => 'RightsLogFormatter', 'rights/autopromote' => 'RightsLogFormatter', - 'upload/upload' => 'LogFormatter', - 'upload/overwrite' => 'LogFormatter', - 'upload/revert' => 'LogFormatter', + 'upload/upload' => 'UploadLogFormatter', + 'upload/overwrite' => 'UploadLogFormatter', + 'upload/revert' => 'UploadLogFormatter', + 'merge/merge' => 'MergeLogFormatter', + 'tag/update' => 'TagLogFormatter', + 'managetags/create' => 'LogFormatter', + 'managetags/delete' => 'LogFormatter', + 'managetags/activate' => 'LogFormatter', + 'managetags/deactivate' => 'LogFormatter', + 'block/block' => 'BlockLogFormatter', + 'block/unblock' => 'BlockLogFormatter', + 'block/reblock' => 'BlockLogFormatter', + 'suppress/block' => 'BlockLogFormatter', + 'suppress/reblock' => 'BlockLogFormatter', + 'import/upload' => 'LogFormatter', + 'import/interwiki' => 'LogFormatter', ); /** @@ -6691,6 +6790,7 @@ $wgActions = array( 'credits' => true, 'delete' => true, 'edit' => true, + 'editchangetags' => 'SpecialPageAction', 'history' => true, 'info' => true, 'markpatrolled' => true, @@ -6699,7 +6799,7 @@ $wgActions = array( 'raw' => true, 'render' => true, 'revert' => true, - 'revisiondelete' => true, + 'revisiondelete' => 'SpecialPageAction', 'rollback' => true, 'submit' => true, 'unprotect' => true, @@ -6966,6 +7066,12 @@ $wgAjaxUploadDestCheck = true; */ $wgAjaxLicensePreview = true; +/** + * Have clients send edits to be prepared when filling in edit summaries. + * This gives the server a head start on the expensive parsing operation. + */ +$wgAjaxEditStash = true; + /** * Settings for incoming cross-site AJAX requests: * Newer browsers support cross-site AJAX when the target resource allows requests @@ -7083,6 +7189,18 @@ $wgAsyncHTTPTimeout = 25; */ $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. + * @since 1.25 + */ +$wgLocalVirtualHosts = array(); + /** * Timeout for connections done internally (in seconds) * Only works for curl @@ -7286,13 +7404,21 @@ $wgPagePropsHaveSortkey = true; $wgHttpsPort = 443; /** - * Secret and algorithm for hmac-based key derivation function (fast, + * Secret for hmac-based key derivation function (fast, * cryptographically secure random numbers). * This should be set in LocalSettings.php, otherwise wgSecretKey will * be used. + * See also: $wgHKDFAlgorithm * @since 1.24 */ $wgHKDFSecret = false; + +/** + * Algorithm for hmac-based key derivation function (fast, + * cryptographically secure random numbers). + * See also: $wgHKDFSecret + * @since 1.24 + */ $wgHKDFAlgorithm = 'sha256'; /** @@ -7311,6 +7437,34 @@ $wgPageLanguageUseDB = false; */ $wgUseLinkNamespaceDBFields = true; +/** + * Global configuration variable for Virtual REST Services. + * Parameters for different services are to be declared inside + * $wgVirtualRestConfig['modules'], which is to be treated as an associative + * array. Global parameters will be merged with service-specific ones. The + * result will then be passed to VirtualRESTService::__construct() in the + * module. + * + * Example config for Parsoid: + * + * $wgVirtualRestConfig['modules']['parsoid'] = array( + * 'url' => 'http://localhost:8000', + * 'prefix' => 'enwiki', + * ); + * + * @var array + * @since 1.25 + */ +$wgVirtualRestConfig = array( + 'modules' => array(), + 'global' => array( + # Timeout in seconds + 'timeout' => 360, + 'forwardCookies' => false, + 'HTTPProxy' => null + ) +); + /** * For really cool vim folding this needs to be at the end: * vim: foldmarker=@{,@} foldmethod=marker diff --git a/includes/Defines.php b/includes/Defines.php index 017e9ea4..c9263da9 100644 --- a/includes/Defines.php +++ b/includes/Defines.php @@ -2,10 +2,6 @@ /** * A few constants that might be needed during LocalSettings.php. * - * Note: these constants must all be resolvable at compile time by HipHop, - * since this file will not be executed during request startup for a compiled - * MediaWiki. - * * 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 @@ -152,12 +148,13 @@ define( 'AV_SCAN_FAILED', false ); #scan failed (scanner not found or error in /**@{ * Anti-lock flags - * See DefaultSettings.php for a description + * Was used by $wgAntiLockFlags, which was removed with 1.25 + * Constants kept to not have warnings when used in LocalSettings */ define( 'ALF_PRELOAD_LINKS', 1 ); // unused define( 'ALF_PRELOAD_EXISTENCE', 2 ); // unused -define( 'ALF_NO_LINK_LOCK', 4 ); -define( 'ALF_NO_BLOCK_LOCK', 8 ); +define( 'ALF_NO_LINK_LOCK', 4 ); // unused +define( 'ALF_NO_BLOCK_LOCK', 8 ); // unused /**@}*/ /**@{ @@ -206,15 +203,15 @@ define( 'LIST_OR', 4 ); /** * Unicode and normalisation related */ -require_once __DIR__ . '/normal/UtfNormalDefines.php'; +require_once __DIR__ . '/libs/normal/UtfNormalDefines.php'; /**@{ * Hook support constants */ -define( 'MW_SUPPORTS_EDITFILTERMERGED', 1 ); define( 'MW_SUPPORTS_PARSERFIRSTCALLINIT', 1 ); define( 'MW_SUPPORTS_LOCALISATIONCACHE', 1 ); define( 'MW_SUPPORTS_CONTENTHANDLER', 1 ); +define( 'MW_EDITFILTERMERGED_SUPPORTS_API', 1 ); /**@}*/ /** Support for $wgResourceModules */ @@ -223,6 +220,12 @@ define( 'MW_SUPPORTS_RESOURCE_MODULES', 1 ); /**@{ * Allowed values for Parser::$mOutputType * Parameter to Parser::startExternalParse(). + * Use of Parser consts is preferred: + * - Parser::OT_HTML + * - Parser::OT_WIKI + * - Parser::OT_PREPROCESS + * - Parser::OT_MSG + * - Parser::OT_PLAIN */ define( 'OT_HTML', 1 ); define( 'OT_WIKI', 2 ); @@ -233,16 +236,14 @@ define( 'OT_PLAIN', 4 ); /**@{ * Flags for Parser::setFunctionHook + * Use of Parser consts is preferred: + * - Parser::SFH_NO_HASH + * - Parser::SFH_OBJECT_ARGS */ define( 'SFH_NO_HASH', 1 ); define( 'SFH_OBJECT_ARGS', 2 ); /**@}*/ -/** - * Flags for Parser::replaceLinkHolders - */ -define( 'RLH_FOR_UPDATE', 1 ); - /**@{ * Autopromote conditions (must be here and not in Autopromote.php, so that * they're loaded for DefaultSettings.php before AutoLoader.php) diff --git a/includes/EditPage.php b/includes/EditPage.php index 38c80ba8..8d27eac8 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -150,6 +150,18 @@ class EditPage { */ const AS_NO_CHANGE_CONTENT_MODEL = 235; + /** + * Status: user tried to create self-redirect (redirect to the same article) and + * wpIgnoreSelfRedirect == false + */ + const AS_SELF_REDIRECT = 236; + + /** + * Status: an error relating to change tagging. Look at the message key for + * more details + */ + const AS_CHANGE_TAG_ERROR = 237; + /** * Status: can't parse content */ @@ -256,6 +268,12 @@ class EditPage { /** @var bool */ protected $allowBlankArticle = false; + /** @var bool */ + protected $selfRedirect = false; + + /** @var bool */ + protected $allowSelfRedirect = false; + /** @var string */ public $autoSumm = ''; @@ -321,6 +339,9 @@ class EditPage { /** @var int */ public $oldid = 0; + /** @var int */ + public $parentRevId = 0; + /** @var string */ public $editintro = ''; @@ -336,6 +357,9 @@ class EditPage { /** @var null|string */ public $contentFormat = null; + /** @var null|array */ + public $changeTags = null; + # Placeholders for text injection by hooks (must be HTML) # extensions should take care to _append_ to the present value @@ -362,9 +386,6 @@ class EditPage { /** @var bool */ protected $edit; - /** @var bool */ - public $live; - /** * @param Article $article */ @@ -448,29 +469,21 @@ class EditPage { function edit() { global $wgOut, $wgRequest, $wgUser; // Allow extensions to modify/prevent this form or submission - if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) { + if ( !Hooks::run( 'AlternateEdit', array( $this ) ) ) { return; } - wfProfileIn( __METHOD__ ); wfDebug( __METHOD__ . ": enter\n" ); // If they used redlink=1 and the page exists, redirect to the main article if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) { $wgOut->redirect( $this->mTitle->getFullURL() ); - wfProfileOut( __METHOD__ ); return; } $this->importFormData( $wgRequest ); $this->firsttime = false; - if ( $this->live ) { - $this->livePreview(); - wfProfileOut( __METHOD__ ); - return; - } - if ( wfReadOnly() && $this->save ) { // Force preview $this->save = false; @@ -492,7 +505,7 @@ class EditPage { } } - $permErrors = $this->getEditPermissionErrors(); + $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' ); if ( $permErrors ) { wfDebug( __METHOD__ . ": User can't edit\n" ); // Auto-block user's IP if the account was "hard" blocked @@ -500,12 +513,9 @@ class EditPage { $this->displayPermissionsError( $permErrors ); - wfProfileOut( __METHOD__ ); return; } - wfProfileIn( __METHOD__ . "-business-end" ); - $this->isConflict = false; // css / js subpages of user pages get a special treatment $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage(); @@ -525,9 +535,9 @@ class EditPage { # in the back door with a hand-edited submission URL. if ( 'save' == $this->formtype ) { - if ( !$this->attemptSave() ) { - wfProfileOut( __METHOD__ . "-business-end" ); - wfProfileOut( __METHOD__ ); + $resultDetails = null; + $status = $this->attemptSave( $resultDetails ); + if ( !$this->handleStatus( $status, $resultDetails ) ) { return; } } @@ -537,34 +547,37 @@ class EditPage { if ( 'initial' == $this->formtype || $this->firsttime ) { if ( $this->initialiseForm() === false ) { $this->noSuchSectionPage(); - wfProfileOut( __METHOD__ . "-business-end" ); - wfProfileOut( __METHOD__ ); return; } if ( !$this->mTitle->getArticleID() ) { - wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) ); + Hooks::run( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) ); } else { - wfRunHooks( 'EditFormInitialText', array( $this ) ); + Hooks::run( 'EditFormInitialText', array( $this ) ); } } $this->showEditForm(); - wfProfileOut( __METHOD__ . "-business-end" ); - wfProfileOut( __METHOD__ ); } /** + * @param string $rigor Same format as Title::getUserPermissionErrors() * @return array */ - protected function getEditPermissionErrors() { + protected function getEditPermissionErrors( $rigor = 'secure' ) { global $wgUser; - $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser ); + + $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser, $rigor ); # Can this title be created? if ( !$this->mTitle->exists() ) { - $permErrors = array_merge( $permErrors, - wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) ); + $permErrors = array_merge( + $permErrors, + wfArrayDiff2( + $this->mTitle->getUserPermissionsErrors( 'create', $wgUser, $rigor ), + $permErrors + ) + ); } # Ignore some permissions errors when a user is just previewing/viewing diffs $remove = array(); @@ -576,6 +589,7 @@ class EditPage { } } $permErrors = wfArrayDiff2( $permErrors, $remove ); + return $permErrors; } @@ -612,7 +626,7 @@ class EditPage { throw new PermissionsError( $action, $permErrors ); } - wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) ); + Hooks::run( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setPageTitle( wfMessage( @@ -717,13 +731,10 @@ class EditPage { function importFormData( &$request ) { global $wgContLang, $wgUser; - wfProfileIn( __METHOD__ ); - # Section edit can come from either the form or a link $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) ); if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) { - wfProfileOut( __METHOD__ ); throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); } @@ -738,13 +749,10 @@ class EditPage { // Skip this if wpTextbox2 has input, it indicates that we came // from a conflict page with raw page text, not a custom form // modified by subclasses - wfProfileIn( get_class( $this ) . "::importContentFormData" ); $textbox1 = $this->importContentFormData( $request ); if ( $textbox1 !== null ) { $this->textbox1 = $textbox1; } - - wfProfileOut( get_class( $this ) . "::importContentFormData" ); } # Truncate for whole multibyte characters @@ -785,7 +793,8 @@ class EditPage { // TODO: softened the check for cutover. Once we determine // that it is safe, we should complete the transition by // removing the "edittime" clause. - $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' ) && is_null( $this->edittime ) ); + $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' ) + && is_null( $this->edittime ) ); } if ( $this->incompleteForm ) { # If the form is incomplete, force to preview. @@ -793,8 +802,7 @@ class EditPage { wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" ); $this->preview = true; } else { - /* Fallback for live preview */ - $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' ); + $this->preview = $request->getCheck( 'wpPreview' ); $this->diff = $request->getCheck( 'wpDiff' ); // Remember whether a save was requested, so we can indicate @@ -844,6 +852,15 @@ class EditPage { $this->autoSumm = $request->getText( 'wpAutoSummary' ); $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' ); + $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' ); + + $changeTags = $request->getVal( 'wpChangeTags' ); + if ( is_null( $changeTags ) || $changeTags === '' ) { + $this->changeTags = array(); + } else { + $this->changeTags = array_filter( array_map( 'trim', explode( ',', + $changeTags ) ) ); + } } else { # Not a posted form? Start with nothing. wfDebug( __METHOD__ . ": Not a posted form.\n" ); @@ -880,6 +897,7 @@ class EditPage { } $this->oldid = $request->getInt( 'oldid' ); + $this->parentRevId = $request->getInt( 'parentRevId' ); $this->bot = $request->getBool( 'bot', true ); $this->nosummary = $request->getBool( 'nosummary' ); @@ -905,15 +923,13 @@ class EditPage { * allowed. */ - $this->live = $request->getCheck( 'live' ); $this->editintro = $request->getText( 'editintro', // Custom edit intro for new sections $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' ); // Allow extensions to modify form data - wfRunHooks( 'EditPage::importFormData', array( $this, $request ) ); + Hooks::run( 'EditPage::importFormData', array( $this, $request ) ); - wfProfileOut( __METHOD__ ); } /** @@ -974,8 +990,6 @@ class EditPage { protected function getContentObject( $def_content = null ) { global $wgOut, $wgRequest, $wgUser, $wgContLang; - wfProfileIn( __METHOD__ ); - $content = false; // For message page not locally set, use the i18n message. @@ -1087,7 +1101,6 @@ class EditPage { } } - wfProfileOut( __METHOD__ ); return $content; } @@ -1280,18 +1293,20 @@ class EditPage { /** * Attempt submission + * @param array $resultDetails See docs for $result in internalAttemptSave * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError - * @return bool False if output is done, true if the rest of the form should be displayed + * @return Status The resulting status object. */ - public function attemptSave() { + public function attemptSave( &$resultDetails = false ) { global $wgUser; - $resultDetails = false; # Allow bots to exempt some edits from bot flagging $bot = $wgUser->isAllowed( 'bot' ) && $this->bot; $status = $this->internalAttemptSave( $resultDetails, $bot ); - return $this->handleStatus( $status, $resultDetails ); + Hooks::run( 'EditPage::attemptSave:after', array( $this, $status, $resultDetails ) ); + + return $status; } /** @@ -1329,6 +1344,7 @@ class EditPage { case self::AS_MAX_ARTICLE_SIZE_EXCEEDED: case self::AS_END: case self::AS_BLANK_ARTICLE: + case self::AS_SELF_REDIRECT: return true; case self::AS_HOOK_ERROR: @@ -1349,7 +1365,7 @@ class EditPage { $sectionanchor = $resultDetails['sectionanchor']; // Give extensions a chance to modify URL query on update - wfRunHooks( + Hooks::run( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) ); @@ -1415,8 +1431,8 @@ class EditPage { protected function runPostMergeFilters( Content $content, Status $status, User $user ) { // Run old style post-section-merge edit filter if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', - array( $this, $content, &$this->hookError, $this->summary ) ) ) { - + array( $this, $content, &$this->hookError, $this->summary ) ) + ) { # Error messages etc. could be handled within the hook... $status->fatal( 'hookaborted' ); $status->value = self::AS_HOOK_ERROR; @@ -1429,15 +1445,24 @@ class EditPage { } // Run new style post-section-merge edit filter - if ( !wfRunHooks( 'EditFilterMergedContent', - array( $this->mArticle->getContext(), $content, $status, $this->summary, - $user, $this->minoredit ) ) ) { - + if ( !Hooks::run( 'EditFilterMergedContent', + array( $this->mArticle->getContext(), $content, $status, $this->summary, + $user, $this->minoredit ) ) + ) { # Error messages etc. could be handled within the hook... - // XXX: $status->value may already be something informative... - $this->hookError = $status->getWikiText(); - $status->fatal( 'hookaborted' ); - $status->value = self::AS_HOOK_ERROR; + if ( $status->isGood() ) { + $status->fatal( 'hookaborted' ); + // Not setting $this->hookError here is a hack to allow the hook + // to cause a return to the edit page without $this->hookError + // being set. This is used by ConfirmEdit to display a captcha + // without any error message cruft. + } else { + $this->hookError = $status->getWikiText(); + } + // Use the existing $status->value if the hook set it + if ( !$status->value ) { + $status->value = self::AS_HOOK_ERROR; + } return false; } elseif ( !$status->isOK() ) { # ...or the hook could be expecting us to produce an error @@ -1510,15 +1535,10 @@ class EditPage { $status = Status::newGood(); - wfProfileIn( __METHOD__ ); - wfProfileIn( __METHOD__ . '-checks' ); - - if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) { + if ( !Hooks::run( 'EditPage::attemptSave', array( $this ) ) ) { wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" ); $status->fatal( 'hookaborted' ); $status->value = self::AS_HOOK_ERROR; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1535,8 +1555,6 @@ class EditPage { ); $status->fatal( 'spamprotectionmatch', false ); $status->value = self::AS_SPAM_ERROR; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1551,8 +1569,6 @@ class EditPage { $ex->getMessage() ); $status->value = self::AS_PARSE_ERROR; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1564,9 +1580,6 @@ class EditPage { $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; $status->setResult( false, $code ); - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); - return $status; } @@ -1595,26 +1608,20 @@ class EditPage { wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" ); $status->fatal( 'spamprotectionmatch', $match ); $status->value = self::AS_SPAM_ERROR; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } - if ( !wfRunHooks( + if ( !Hooks::run( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) { # Error messages etc. could be handled within the hook... $status->fatal( 'hookaborted' ); $status->value = self::AS_HOOK_ERROR; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } elseif ( $this->hookError != '' ) { # ...or the hook could be expecting us to produce an error $status->fatal( 'hookaborted' ); $status->value = self::AS_HOOK_ERROR_EXPECTED; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1623,8 +1630,6 @@ class EditPage { $wgUser->spreadAnyEditBlock(); # Check block state against master, thus 'false'. $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER ); - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1633,22 +1638,16 @@ class EditPage { // Error will be displayed by showEditForm() $this->tooBig = true; $status->setResult( false, self::AS_CONTENT_TOO_BIG ); - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } if ( !$wgUser->isAllowed( 'edit' ) ) { if ( $wgUser->isAnon() ) { $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON ); - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } else { $status->fatal( 'readonlytext' ); $status->value = self::AS_READ_ONLY_PAGE_LOGGED; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } } @@ -1657,23 +1656,26 @@ class EditPage { && !$wgUser->isAllowed( 'editcontentmodel' ) ) { $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL ); - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } + if ( $this->changeTags ) { + $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange( + $this->changeTags, $wgUser ); + if ( !$changeTagsStatus->isOK() ) { + $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR; + return $changeTagsStatus; + } + } + if ( wfReadOnly() ) { $status->fatal( 'readonlytext' ); $status->value = self::AS_READ_ONLY_PAGE; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) { $status->fatal( 'actionthrottledtext' ); $status->value = self::AS_RATE_LIMITED; - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1681,13 +1683,9 @@ class EditPage { # confirmation if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) { $status->setResult( false, self::AS_ARTICLE_WAS_DELETED ); - wfProfileOut( __METHOD__ . '-checks' ); - wfProfileOut( __METHOD__ ); return $status; } - wfProfileOut( __METHOD__ . '-checks' ); - # Load the page data from the master. If anything changes in the meantime, # we detect it by using page_latest like a token in a 1 try compare-and-swap. $this->mArticle->loadPageData( 'fromdbmaster' ); @@ -1699,7 +1697,6 @@ class EditPage { $status->fatal( 'nocreatetext' ); $status->value = self::AS_NO_CREATE_PERMISSION; wfDebug( __METHOD__ . ": no create permission\n" ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1717,12 +1714,10 @@ class EditPage { $this->blankArticle = true; $status->fatal( 'blankarticle' ); $status->setResult( false, self::AS_BLANK_ARTICLE ); - wfProfileOut( __METHOD__ ); return $status; } if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) { - wfProfileOut( __METHOD__ ); return $status; } @@ -1827,12 +1822,10 @@ class EditPage { if ( $this->isConflict ) { $status->setResult( false, self::AS_CONFLICT_DETECTED ); - wfProfileOut( __METHOD__ ); return $status; } if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) { - wfProfileOut( __METHOD__ ); return $status; } @@ -1842,7 +1835,6 @@ class EditPage { $this->missingSummary = true; $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh $status->value = self::AS_SUMMARY_NEEDED; - wfProfileOut( __METHOD__ ); return $status; } @@ -1851,7 +1843,6 @@ class EditPage { $this->missingComment = true; $status->fatal( 'missingcommenttext' ); $status->value = self::AS_TEXTBOX_EMPTY; - wfProfileOut( __METHOD__ ); return $status; } } elseif ( !$this->allowBlankSummary @@ -1862,12 +1853,10 @@ class EditPage { $this->missingSummary = true; $status->fatal( 'missingsummary' ); $status->value = self::AS_SUMMARY_NEEDED; - wfProfileOut( __METHOD__ ); return $status; } # All's well - wfProfileIn( __METHOD__ . '-sectionanchor' ); $sectionanchor = ''; if ( $this->section == 'new' ) { $this->summary = $this->newSectionSummary( $sectionanchor ); @@ -1884,7 +1873,6 @@ class EditPage { } } $result['sectionanchor'] = $sectionanchor; - wfProfileOut( __METHOD__ . '-sectionanchor' ); // Save errors may fall down to the edit form, but we've now // merged the section into full text. Clear the section field @@ -1896,12 +1884,25 @@ class EditPage { $status->value = self::AS_SUCCESS_UPDATE; } + if ( !$this->allowSelfRedirect + && $content->isRedirect() + && $content->getRedirectTarget()->equals( $this->getTitle() ) + ) { + // If the page already redirects to itself, don't warn. + $currentTarget = $this->getCurrentContent()->getRedirectTarget(); + if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) { + $this->selfRedirect = true; + $status->fatal( 'selfredirect' ); + $status->value = self::AS_SELF_REDIRECT; + return $status; + } + } + // Check for length errors again now that the section is merged in $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 ); if ( $this->kblength > $wgMaxArticleSize ) { $this->tooBig = true; $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED ); - wfProfileOut( __METHOD__ ); return $status; } @@ -1931,7 +1932,6 @@ class EditPage { // Destroys data doEdit() put in $status->value but who cares $doEditStatus->value = self::AS_END; } - wfProfileOut( __METHOD__ ); return $doEditStatus; } @@ -1941,8 +1941,18 @@ class EditPage { $wgUser->pingLimiter( 'linkpurge' ); } $result['redirect'] = $content->isRedirect(); + $this->updateWatchlist(); - wfProfileOut( __METHOD__ ); + + if ( $this->changeTags && isset( $doEditStatus->value['revision'] ) ) { + // If a revision was created, apply any change tags that were requested + ChangeTags::addTags( + $this->changeTags, + isset( $doEditStatus->value['rc'] ) ? $doEditStatus->value['rc']->mAttribs['rc_id'] : null, + $doEditStatus->value['revision']->getId() + ); + } + return $status; } @@ -1979,7 +1989,6 @@ class EditPage { * @return bool */ private function mergeChangesIntoContent( &$editContent ) { - wfProfileIn( __METHOD__ ); $db = wfGetDB( DB_MASTER ); @@ -1988,7 +1997,6 @@ class EditPage { $baseContent = $baseRevision ? $baseRevision->getContent() : null; if ( is_null( $baseContent ) ) { - wfProfileOut( __METHOD__ ); return false; } @@ -1997,7 +2005,6 @@ class EditPage { $currentContent = $currentRevision ? $currentRevision->getContent() : null; if ( is_null( $currentContent ) ) { - wfProfileOut( __METHOD__ ); return false; } @@ -2007,11 +2014,9 @@ class EditPage { if ( $result ) { $editContent = $result; - wfProfileOut( __METHOD__ ); return true; } - wfProfileOut( __METHOD__ ); return false; } @@ -2070,19 +2075,31 @@ class EditPage { } function setHeaders() { - global $wgOut, $wgUser; + global $wgOut, $wgUser, $wgAjaxEditStash; $wgOut->addModules( 'mediawiki.action.edit' ); $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' ); - if ( $wgUser->getOption( 'uselivepreview', false ) ) { + if ( $wgUser->getOption( 'showtoolbar' ) ) { + // The addition of default buttons is handled by getEditToolbar() which + // has its own dependency on this module. The call here ensures the module + // is loaded in time (it has position "top") for other modules to register + // buttons (e.g. extensions, gadgets, user scripts). + $wgOut->addModules( 'mediawiki.toolbar' ); + } + + if ( $wgUser->getOption( 'uselivepreview' ) ) { $wgOut->addModules( 'mediawiki.action.edit.preview' ); } - if ( $wgUser->getOption( 'useeditwarning', false ) ) { + if ( $wgUser->getOption( 'useeditwarning' ) ) { $wgOut->addModules( 'mediawiki.action.edit.editWarning' ); } + if ( $wgAjaxEditStash ) { + $wgOut->addModules( 'mediawiki.action.edit.stash' ); + } + $wgOut->setRobotPolicy( 'noindex,nofollow' ); # Enabled article-related sidebar, toplinks, etc. @@ -2108,6 +2125,9 @@ class EditPage { $displayTitle = $contextTitle->getPrefixedText(); } $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) ); + # Transmit the name of the message to JavaScript for live preview + # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys + $wgOut->addJsConfigVars( 'wgEditMessage', $msg ); } /** @@ -2124,6 +2144,17 @@ class EditPage { if ( $namespace == NS_MEDIAWIKI ) { # Show a warning if editing an interface message $wgOut->wrapWikiMsg( "
\n$1\n
", 'editinginterface' ); + # If this is a default message (but not css or js), + # show a hint that it is translatable on translatewiki.net + if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS ) + && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) + ) { + $defaultMessageText = $this->mTitle->getDefaultMessageText(); + if ( $defaultMessageText !== false ) { + $wgOut->wrapWikiMsg( "
\n$1\n
", + 'translateinterface' ); + } + } } elseif ( $namespace == NS_FILE ) { # Show a hint to shared repo $file = wfFindFile( $this->mTitle ); @@ -2155,7 +2186,8 @@ class EditPage { if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist $wgOut->wrapWikiMsg( "
\n$1\n
", array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) ); - } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked + } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { + # Show log extract if the user is currently blocked LogEventsList::showLogExtract( $wgOut, 'block', @@ -2222,7 +2254,10 @@ class EditPage { if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) { global $wgOut; // Added using template syntax, to take 's into account. - $wgOut->addWikiTextTitleTidy( '
{{:' . $title->getFullText() . '}}
', $this->mTitle ); + $wgOut->addWikiTextTitleTidy( + '
{{:' . $title->getFullText() . '}}
', + $this->mTitle + ); return true; } } @@ -2248,11 +2283,7 @@ class EditPage { * $this->allowNonTextContent is not true. */ protected function toEditText( $content ) { - if ( $content === null || $content === false ) { - return $content; - } - - if ( is_string( $content ) ) { + if ( $content === null || $content === false || is_string( $content ) ) { return $content; } @@ -2300,12 +2331,13 @@ class EditPage { * Send the edit form and related headers to $wgOut * @param callable|null $formCallback That takes an OutputPage parameter; will be called * during form output near the top, for captchas and the like. + * + * The $formCallback parameter is deprecated since MediaWiki 1.25. Please + * use the EditPage::showEditForm:fields hook instead. */ function showEditForm( $formCallback = null ) { global $wgOut, $wgUser; - wfProfileIn( __METHOD__ ); - # need to parse the preview early so that we know which templates are used, # otherwise users with "show preview after edit box" will get a blank list # we parse this near the beginning so that setHeaders can do the title @@ -2315,12 +2347,11 @@ class EditPage { $previewOutput = $this->getPreviewText(); } - wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) ); + Hooks::run( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) ); $this->setHeaders(); if ( $this->showHeader() === false ) { - wfProfileOut( __METHOD__ ); return; } @@ -2358,6 +2389,7 @@ class EditPage { ) ); if ( is_callable( $formCallback ) ) { + wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' ); call_user_func_array( $formCallback, array( &$wgOut ) ); } @@ -2381,7 +2413,7 @@ class EditPage { . Xml::closeElement( 'div' ) ); - wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); + Hooks::run( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); // Put these up at the top to ensure they aren't lost on early form submission $this->showFormBeforeText(); @@ -2425,6 +2457,10 @@ class EditPage { $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) ); } + if ( $this->selfRedirect ) { + $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) ); + } + if ( $this->hasPresetSummary ) { // If a summary has been preset using &summary= we don't want to prompt for // a different summary. Only prompt for a summary if the summary is blanked. @@ -2436,6 +2472,8 @@ class EditPage { $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) ); $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) ); + $wgOut->addHTML( Html::hidden( 'parentRevId', + $this->parentRevId ?: $this->mArticle->getRevIdFetched() ) ); $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) ); $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) ); @@ -2517,7 +2555,6 @@ class EditPage { $this->displayPreviewArea( $previewOutput, false ); } - wfProfileOut( __METHOD__ ); } /** @@ -2548,7 +2585,19 @@ class EditPage { } // Add edit notices - $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) ); + $editNotices = $this->mTitle->getEditNotices( $this->oldid ); + if ( count( $editNotices ) ) { + $wgOut->addHTML( implode( "\n", $editNotices ) ); + } else { + $msg = wfMessage( 'editnotice-notext' ); + if ( !$msg->isDisabled() ) { + $wgOut->addHTML( + '
' + . $msg->parseAsBlock() + . '
' + ); + } + } if ( $this->isConflict ) { $wgOut->wrapWikiMsg( "
\n$1\n
", 'explainconflict' ); @@ -2587,6 +2636,10 @@ class EditPage { $wgOut->wrapWikiMsg( "
\n$1\n
", 'blankarticle' ); } + if ( $this->selfRedirect ) { + $wgOut->wrapWikiMsg( "
\n$1\n
", 'selfredirect' ); + } + if ( $this->hookError !== '' ) { $wgOut->addWikiText( $this->hookError ); } @@ -2846,7 +2899,7 @@ class EditPage { global $wgOut; $section = htmlspecialchars( $this->section ); $wgOut->addHTML( << + @@ -2964,7 +3017,7 @@ HTML ); $pageLang = $this->mTitle->getPageLanguage(); - $attribs['lang'] = $pageLang->getCode(); + $attribs['lang'] = $pageLang->getHtmlCode(); $attribs['dir'] = $pageLang->getDir(); $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) ); @@ -2987,6 +3040,12 @@ HTML if ( $this->formtype == 'preview' ) { $this->showPreview( $previewOutput ); + } else { + // Empty content container for LivePreview + $pageViewLang = $this->mTitle->getPageViewLanguage(); + $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(), + 'class' => 'mw-content-' . $pageViewLang->getDir() ); + $wgOut->addHTML( Html::rawElement( 'div', $attribs ) ); } $wgOut->addHTML( '
' ); @@ -3019,7 +3078,7 @@ HTML } # This hook seems slightly odd here, but makes things more # consistent for extensions. - wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) ); + Hooks::run( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) ); $wgOut->addHTML( $text ); if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { $this->mArticle->closeShowCategory(); @@ -3058,7 +3117,7 @@ HTML if ( $newContent ) { ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) ); - wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) ); + Hooks::run( 'EditPageGetDiffContent', array( $this, &$newContent ) ); $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts ); @@ -3110,7 +3169,7 @@ HTML */ protected function showTosSummary() { $msg = 'editpage-tos-summary'; - wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) ); + Hooks::run( 'EditPageTosSummary', array( $this->mTitle, &$msg ) ); if ( !wfMessage( $msg )->isDisabled() ) { global $wgOut; $wgOut->addHTML( '
' ); @@ -3154,7 +3213,7 @@ HTML '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ); } // Allow for site and per-namespace customization of contribution/copyright notice. - wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) ); + Hooks::run( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) ); return "
\n" . call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n
"; @@ -3172,8 +3231,6 @@ HTML return ''; } - wfProfileIn( __METHOD__ ); - $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ), wfMessage( 'limitreport-title' )->parseAsBlock() ); @@ -3187,7 +3244,7 @@ HTML Html::openElement( 'tbody' ); foreach ( $output->getLimitReportData() as $key => $value ) { - if ( wfRunHooks( 'ParserLimitReportFormat', + if ( Hooks::run( 'ParserLimitReportFormat', array( $key, &$value, &$limitReport, true, true ) ) ) { $keyMsg = wfMessage( $key ); @@ -3208,13 +3265,11 @@ HTML Html::closeElement( 'table' ) . Html::closeElement( 'div' ); - wfProfileOut( __METHOD__ ); - return $limitReport; } protected function showStandardInputs( &$tabindex = 2 ) { - global $wgOut, $wgUseMediaWikiUIEverywhere; + global $wgOut; $wgOut->addHTML( "
\n" ); if ( $this->section != 'new' ) { @@ -3246,10 +3301,8 @@ HTML 'target' => 'helpwindow', 'href' => $edithelpurl, ); - if ( $wgUseMediaWikiUIEverywhere ) { - $attrs['class'] = 'mw-ui-button mw-ui-quiet'; - } - $edithelp = Html::element( 'a', $attrs, wfMessage( 'edithelp' )->text() ) . + $edithelp = Html::linkButton( wfMessage( 'edithelp' )->text(), + $attrs, array( 'mw-ui-quiet' ) ) . wfMessage( 'word-separator' )->escaped() . wfMessage( 'newwindow' )->parse(); @@ -3257,7 +3310,7 @@ HTML $wgOut->addHTML( " {$edithelp}\n" ); $wgOut->addHTML( "
\n" ); - wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) ); + Hooks::run( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) ); $wgOut->addHTML( "
\n" ); } @@ -3269,7 +3322,7 @@ HTML protected function showConflict() { global $wgOut; - if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) { + if ( Hooks::run( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) { $wgOut->wrapWikiMsg( '

$1

', "yourdiff" ); $content1 = $this->toEditContent( $this->textbox1 ); @@ -3292,20 +3345,16 @@ HTML * @return string */ public function getCancelLink() { - global $wgUseMediaWikiUIEverywhere; $cancelParams = array(); if ( !$this->isConflict && $this->oldid > 0 ) { $cancelParams['oldid'] = $this->oldid; } $attrs = array( 'id' => 'mw-editform-cancel' ); - if ( $wgUseMediaWikiUIEverywhere ) { - $attrs['class'] = 'mw-ui-button mw-ui-quiet'; - } return Linker::linkKnown( $this->getContextTitle(), wfMessage( 'cancel' )->parse(), - $attrs, + Html::buttonAttributes( $attrs, array( 'mw-ui-quiet' ) ), $cancelParams ); } @@ -3401,8 +3450,6 @@ HTML global $wgOut, $wgUser, $wgRawHtml, $wgLang; global $wgAllowUserCss, $wgAllowUserJs; - wfProfileIn( __METHOD__ ); - if ( $wgRawHtml && !$this->mTokenOk ) { // Could be an offsite preview attempt. This is very unsafe if // HTML is enabled, as it could be an attack. @@ -3414,7 +3461,6 @@ HTML $parsedNote = $wgOut->parse( "
" . wfMessage( 'session_fail_preview_html' )->text() . "
", true, /* interface */true ); } - wfProfileOut( __METHOD__ ); return $parsedNote; } @@ -3424,11 +3470,10 @@ HTML $content = $this->toEditContent( $this->textbox1 ); $previewHTML = ''; - if ( !wfRunHooks( + if ( !Hooks::run( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) { - wfProfileOut( __METHOD__ ); return $previewHTML; } @@ -3449,7 +3494,6 @@ HTML } $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() ); - $parserOptions->setEditSection( false ); $parserOptions->setIsPreview( true ); $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' ); @@ -3494,20 +3538,26 @@ HTML $hook_args = array( $this, &$content ); ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args ); - wfRunHooks( 'EditPageGetPreviewContent', $hook_args ); + Hooks::run( 'EditPageGetPreviewContent', $hook_args ); $parserOptions->enableLimitReport(); # For CSS/JS pages, we should have called the ShowRawCssJs hook here. # But it's now deprecated, so never mind - $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions ); - $parserOutput = $content->getParserOutput( - $this->getArticle()->getTitle(), - null, - $parserOptions + $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions ); + $scopedCallback = $parserOptions->setupFakeRevision( + $this->mTitle, $pstContent, $wgUser ); + $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions ); + + # Try to stash the edit for the final submission step + # @todo: different date format preferences cause cache misses + ApiStashEdit::stashEditFromPreview( + $this->getArticle(), $content, $pstContent, + $parserOutput, $parserOptions, $parserOptions, wfTimestampNow() ); + $parserOutput->setEditSectionTokens( false ); // no section edit links $previewHTML = $parserOutput->getText(); $this->mParserOutput = $parserOutput; $wgOut->addParserOutputMetadata( $parserOutput ); @@ -3542,7 +3592,6 @@ HTML 'class' => 'mw-content-' . $pageViewLang->getDir() ); $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML ); - wfProfileOut( __METHOD__ ); return $previewhead . $previewHTML . $this->previewTextAfterContent; } @@ -3660,7 +3709,7 @@ HTML ) ); - $script = 'mw.loader.using("mediawiki.action.edit", function() {'; + $script = 'mw.loader.using("mediawiki.toolbar", function () {'; foreach ( $toolarray as $tool ) { if ( !$tool ) { continue; @@ -3680,21 +3729,19 @@ HTML $tool['id'], ); - $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params ); + $script .= Xml::encodeJsCall( + 'mw.toolbar.addButton', + $params, + ResourceLoader::inDebugMode() + ); } - // This used to be called on DOMReady from mediawiki.action.edit, which - // ended up causing race conditions with the setup code above. - $script .= "\n" . - "// Create button bar\n" . - "$(function() { mw.toolbar.init(); } );\n"; - $script .= '});'; $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) ); $toolbar = '
'; - wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) ); + Hooks::run( 'EditPageBeforeEditToolbar', array( &$toolbar ) ); return $toolbar; } @@ -3761,7 +3808,7 @@ HTML $checkboxes['watch'] = $watchThisHtml; } } - wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) ); + Hooks::run( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) ); return $checkboxes; } @@ -3774,82 +3821,38 @@ HTML * @return array */ public function getEditButtons( &$tabindex ) { - global $wgUseMediaWikiUIEverywhere; - $buttons = array(); $attribs = array( 'id' => 'wpSave', 'name' => 'wpSave', - 'type' => 'submit', 'tabindex' => ++$tabindex, - 'value' => wfMessage( 'savearticle' )->text(), ) + Linker::tooltipAndAccesskeyAttribs( 'save' ); - if ( $wgUseMediaWikiUIEverywhere ) { - $attribs['class'] = 'mw-ui-button mw-ui-constructive'; - } - $buttons['save'] = Xml::element( 'input', $attribs, '' ); + $buttons['save'] = Html::submitButton( wfMessage( 'savearticle' )->text(), + $attribs, array( 'mw-ui-constructive' ) ); ++$tabindex; // use the same for preview and live preview $attribs = array( 'id' => 'wpPreview', 'name' => 'wpPreview', - 'type' => 'submit', 'tabindex' => $tabindex, - 'value' => wfMessage( 'showpreview' )->text(), ) + Linker::tooltipAndAccesskeyAttribs( 'preview' ); - if ( $wgUseMediaWikiUIEverywhere ) { - $attribs['class'] = 'mw-ui-button mw-ui-progressive'; - } - $buttons['preview'] = Xml::element( 'input', $attribs, '' ); + $buttons['preview'] = Html::submitButton( wfMessage( 'showpreview' )->text(), + $attribs ); $buttons['live'] = ''; $attribs = array( 'id' => 'wpDiff', 'name' => 'wpDiff', - 'type' => 'submit', 'tabindex' => ++$tabindex, - 'value' => wfMessage( 'showdiff' )->text(), ) + Linker::tooltipAndAccesskeyAttribs( 'diff' ); - if ( $wgUseMediaWikiUIEverywhere ) { - $attribs['class'] = 'mw-ui-button mw-ui-progressive'; - } - $buttons['diff'] = Xml::element( 'input', $attribs, '' ); + $buttons['diff'] = Html::submitButton( wfMessage( 'showdiff' )->text(), + $attribs ); - wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) ); + Hooks::run( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) ); return $buttons; } - /** - * Output preview text only. This can be sucked into the edit page - * via JavaScript, and saves the server time rendering the skin as - * well as theoretically being more robust on the client (doesn't - * disturb the edit box's undo history, won't eat your text on - * failure, etc). - * - * @todo This doesn't include category or interlanguage links. - * Would need to enhance it a bit, "maybe wrap them in XML - * or something..." that might also require more skin - * initialization, so check whether that's a problem. - */ - function livePreview() { - global $wgOut; - $wgOut->disable(); - header( 'Content-type: text/xml; charset=utf-8' ); - header( 'Cache-control: no-cache' ); - - $previewText = $this->getPreviewText(); - #$categories = $skin->getCategoryLinks(); - - $s = - '' . "\n" . - Xml::tags( 'livepreview', null, - Xml::element( 'preview', null, $previewText ) - #. Xml::element( 'category', null, $categories ) - ); - echo $s; - } - /** * Creates a basic error page which informs the user that * they have attempted to edit a nonexistent section. @@ -3860,7 +3863,7 @@ HTML $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) ); $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock(); - wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) ); + Hooks::run( 'EditPageNoSuchSection', array( &$this, &$res ) ); $wgOut->addHTML( $res ); $wgOut->returnToMain( false, $this->mTitle ); @@ -4018,9 +4021,9 @@ HTML // Do some sanity checks. These aren't needed for reversibility, // but should help keep the breakage down if the editor // breaks one of the entities whilst editing. - if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) { + if ( ( substr( $invalue, $i, 1 ) == ";" ) && ( strlen( $hexstring ) <= 6 ) ) { $codepoint = hexdec( $hexstring ); - $result .= codepointToUtf8( $codepoint ); + $result .= UtfNormal\Utils::codepointToUtf8( $codepoint ); } else { $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 ); } diff --git a/includes/Export.php b/includes/Export.php index 84f5c60c..4600feb5 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -69,7 +69,7 @@ class WikiExporter { * @return string */ public static function schemaVersion() { - return "0.9"; + return "0.10"; } /** @@ -213,7 +213,6 @@ class WikiExporter { * @param array $cond */ protected function do_list_authors( $cond ) { - wfProfileIn( __METHOD__ ); $this->author_list = ""; // rev_deleted @@ -239,7 +238,6 @@ class WikiExporter { ""; } $this->author_list .= ""; - wfProfileOut( __METHOD__ ); } /** @@ -248,7 +246,6 @@ class WikiExporter { * @throws Exception */ protected function dumpFrom( $cond = '' ) { - wfProfileIn( __METHOD__ ); # For logging dumps... if ( $this->history & self::LOGS ) { $where = array( 'user_id = log_user' ); @@ -304,7 +301,6 @@ class WikiExporter { } // Inform caller about problem - wfProfileOut( __METHOD__ ); throw $e; } # For page dumps... @@ -348,8 +344,7 @@ class WikiExporter { # Default JOIN, to be overridden... $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ); # One, and only one hook should set this, and return false - if ( wfRunHooks( 'WikiExporter::dumpStableQuery', array( &$tables, &$opts, &$join ) ) ) { - wfProfileOut( __METHOD__ ); + if ( Hooks::run( 'WikiExporter::dumpStableQuery', array( &$tables, &$opts, &$join ) ) ) { throw new MWException( __METHOD__ . " given invalid history dump type." ); } } elseif ( $this->history & WikiExporter::RANGE ) { @@ -358,7 +353,6 @@ class WikiExporter { $opts['ORDER BY'] = array( 'rev_page ASC', 'rev_id ASC' ); } else { # Unknown history specification parameter? - wfProfileOut( __METHOD__ ); throw new MWException( __METHOD__ . " given invalid history dump type." ); } # Query optimization hacks @@ -378,7 +372,7 @@ class WikiExporter { $result = null; // Assuring $result is not undefined, if exception occurs early try { - wfRunHooks( 'ModifyExportQuery', + Hooks::run( 'ModifyExportQuery', array( $this->db, &$tables, &$cond, &$opts, &$join ) ); # Do the query! @@ -417,7 +411,6 @@ class WikiExporter { throw $e; } } - wfProfileOut( __METHOD__ ); } /** @@ -479,16 +472,6 @@ class WikiExporter { * @ingroup Dump */ class XmlDumpWriter { - /** - * Returns the export schema version. - * @deprecated since 1.20; use WikiExporter::schemaVersion() instead - * @return string - */ - function schemaVersion() { - wfDeprecated( __METHOD__, '1.20' ); - return WikiExporter::schemaVersion(); - } - /** * Opens the XML output stream's root "" element. * This does not include an xml directive, so is safe to include @@ -637,7 +620,7 @@ class XmlDumpWriter { strval( $row->page_restrictions ) ) . "\n"; } - wfRunHooks( 'XmlDumpWriterOpenPage', array( $this, &$out, $row, $title ) ); + Hooks::run( 'XmlDumpWriterOpenPage', array( $this, &$out, $row, $title ) ); return $out; } @@ -661,7 +644,6 @@ class XmlDumpWriter { * @access private */ function writeRevision( $row ) { - wfProfileIn( __METHOD__ ); $out = " \n"; $out .= " " . Xml::element( 'id', null, strval( $row->rev_id ) ) . "\n"; @@ -703,6 +685,9 @@ class XmlDumpWriter { $content_format = $content_handler->getDefaultFormat(); } + $out .= " " . Xml::element( 'model', null, strval( $content_model ) ) . "\n"; + $out .= " " . Xml::element( 'format', null, strval( $content_format ) ) . "\n"; + $text = ''; if ( isset( $row->rev_deleted ) && ( $row->rev_deleted & Revision::DELETED_TEXT ) ) { $out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n"; @@ -729,14 +714,10 @@ class XmlDumpWriter { $out .= " \n"; } - $out .= " " . Xml::element( 'model', null, strval( $content_model ) ) . "\n"; - $out .= " " . Xml::element( 'format', null, strval( $content_format ) ) . "\n"; - - wfRunHooks( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) ); + Hooks::run( 'XmlDumpWriterWriteRevision', array( &$this, &$out, $row, $text ) ); $out .= " \n"; - wfProfileOut( __METHOD__ ); return $out; } @@ -749,7 +730,6 @@ class XmlDumpWriter { * @access private */ function writeLogItem( $row ) { - wfProfileIn( __METHOD__ ); $out = " \n"; $out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n"; @@ -783,7 +763,6 @@ class XmlDumpWriter { $out .= " \n"; - wfProfileOut( __METHOD__ ); return $out; } diff --git a/includes/Feed.php b/includes/Feed.php index 2fdfa424..600b136d 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -92,7 +92,7 @@ class FeedItem { */ public function getUniqueId() { if ( $this->uniqueId ) { - return $this->xmlEncode( $this->uniqueId ); + return $this->xmlEncode( wfExpandUrl( $this->uniqueId, PROTO_CURRENT ) ); } } @@ -184,7 +184,8 @@ class FeedItem { } /** - * @todo document (needs one-sentence top-level class description). + * Class to support the outputting of syndication feeds in Atom and RSS format. + * * @ingroup Feed */ abstract class ChannelFeed extends FeedItem { @@ -338,13 +339,14 @@ class RSSFeed extends ChannelFeed { */ class AtomFeed extends ChannelFeed { /** - * @todo document - * @param string|int $ts + * Format a date given timestamp. + * + * @param string|int $timestamp * @return string */ - function formatTime( $ts ) { + function formatTime( $timestamp ) { // need to use RFC 822 time format at least for rss2.0 - return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $ts ) ); + return gmdate( 'Y-m-d\TH:i:s', wfTimestamp( TS_UNIX, $timestamp ) ); } /** diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php index 6937c32d..a01d6420 100644 --- a/includes/FeedUtils.php +++ b/includes/FeedUtils.php @@ -106,7 +106,6 @@ class FeedUtils { $comment, $actiontext = '' ) { global $wgFeedDiffCutoff, $wgLang; - wfProfileIn( __METHOD__ ); // log entries $completeText = '

' . implode( ' ', @@ -124,12 +123,10 @@ class FeedUtils { // Can't diff special pages, unreadable pages or pages with no new revision // to compare against: just return the text. if ( $title->getNamespace() < 0 || $accErrors || !$newid ) { - wfProfileOut( __METHOD__ ); return $completeText; } if ( $oldid ) { - wfProfileIn( __METHOD__ . "-dodiff" ); #$diffText = $de->getDiff( wfMessage( 'revisionasof', # $wgLang->timeanddate( $timestamp ), @@ -167,10 +164,9 @@ class FeedUtils { $diffText = "

Can't load revision $newid

"; } else { // Diff output fine, clean up any illegal UTF-8 - $diffText = UtfNormal::cleanUp( $diffText ); + $diffText = UtfNormal\Validator::cleanUp( $diffText ); $diffText = self::applyDiffStyle( $diffText ); } - wfProfileOut( __METHOD__ . "-dodiff" ); } else { $rev = Revision::newFromId( $newid ); if ( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) { @@ -208,7 +204,6 @@ class FeedUtils { } $completeText .= $diffText; - wfProfileOut( __METHOD__ ); return $completeText; } diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php index b4e24581..c1d14db0 100644 --- a/includes/FileDeleteForm.php +++ b/includes/FileDeleteForm.php @@ -201,7 +201,7 @@ class FileDeleteForm { $dbw->rollback( __METHOD__ ); } } - } catch ( MWException $e ) { + } catch ( Exception $e ) { // Rollback before returning to prevent UI from displaying // incorrect "View or restore N deleted edits?" $dbw->rollback( __METHOD__ ); @@ -210,7 +210,7 @@ class FileDeleteForm { } if ( $status->isOK() ) { - wfRunHooks( 'FileDeleteComplete', array( &$file, &$oldimage, &$page, &$user, &$reason ) ); + Hooks::run( 'FileDeleteComplete', array( &$file, &$oldimage, &$page, &$user, &$reason ) ); } return $status; diff --git a/includes/GitInfo.php b/includes/GitInfo.php index 7052820e..fb298cfe 100644 --- a/includes/GitInfo.php +++ b/includes/GitInfo.php @@ -392,7 +392,7 @@ class GitInfo { if ( self::$viewers === false ) { self::$viewers = $wgGitRepositoryViewers; - wfRunHooks( 'GitViewers', array( &self::$viewers ) ); + Hooks::run( 'GitViewers', array( &self::$viewers ) ); } return self::$viewers; diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 27f7cacb..240cb97b 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -24,13 +24,17 @@ if ( !defined( 'MEDIAWIKI' ) ) { die( "This file is part of MediaWiki, it is not a valid entry point" ); } +use Liuggio\StatsdClient\StatsdClient; +use Liuggio\StatsdClient\Sender\SocketSender; +use MediaWiki\Logger\LoggerFactory; + // Hide compatibility functions from Doxygen /// @cond /** * Compatibility functions * - * We support PHP 5.3.2 and up. + * We support PHP 5.3.3 and up. * Re-implementations of newer functions or functions in non-standard * PHP extensions may be included here. */ @@ -160,6 +164,72 @@ if ( !function_exists( 'hash_equals' ) ) { } /// @endcond +/** + * Load an extension + * + * This queues an extension to be loaded through + * the ExtensionRegistry system. + * + * @param string $ext Name of the extension to load + * @param string|null $path Absolute path of where to find the extension.json file + */ +function wfLoadExtension( $ext, $path = null ) { + if ( !$path ) { + global $wgExtensionDirectory; + $path = "$wgExtensionDirectory/$ext/extension.json"; + } + ExtensionRegistry::getInstance()->queue( $path ); +} + +/** + * Load multiple extensions at once + * + * Same as wfLoadExtension, but more efficient if you + * are loading multiple extensions. + * + * If you want to specify custom paths, you should interact with + * ExtensionRegistry directly. + * + * @see wfLoadExtension + * @param string[] $exts Array of extension names to load + */ +function wfLoadExtensions( array $exts ) { + global $wgExtensionDirectory; + $registry = ExtensionRegistry::getInstance(); + foreach ( $exts as $ext ) { + $registry->queue( "$wgExtensionDirectory/$ext/extension.json" ); + } +} + +/** + * Load a skin + * + * @see wfLoadExtension + * @param string $skin Name of the extension to load + * @param string|null $path Absolute path of where to find the skin.json file + */ +function wfLoadSkin( $skin, $path = null ) { + if ( !$path ) { + global $wgStyleDirectory; + $path = "$wgStyleDirectory/$skin/skin.json"; + } + ExtensionRegistry::getInstance()->queue( $path ); +} + +/** + * Load multiple skins at once + * + * @see wfLoadExtensions + * @param string[] $skins Array of extension names to load + */ +function wfLoadSkins( array $skins ) { + global $wgStyleDirectory; + $registry = ExtensionRegistry::getInstance(); + foreach ( $skins as $skin ) { + $registry->queue( "$wgStyleDirectory/$skin/skin.json" ); + } +} + /** * Like array_diff( $a, $b ) except that it works with two-dimensional arrays. * @param array $a @@ -950,44 +1020,40 @@ function wfMatchesDomainList( $url, $domains ) { * $wgDebugRawPage - if false, 'action=raw' hits will not result in debug output. * $wgDebugComments - if on, some debug items may appear in comments in the HTML output. * + * @since 1.25 support for additional context data + * * @param string $text - * @param string|bool $dest Destination of the message: - * - 'all': both to the log and HTML (debug toolbar or HTML comments) - * - 'log': only to the log and not in HTML - * For backward compatibility, it can also take a boolean: - * - true: same as 'all' - * - false: same as 'log' + * @param string|bool $dest Unused + * @param array $context Additional logging context data */ -function wfDebug( $text, $dest = 'all' ) { - global $wgDebugLogFile, $wgDebugRawPage, $wgDebugLogPrefix; +function wfDebug( $text, $dest = 'all', array $context = array() ) { + global $wgDebugRawPage, $wgDebugLogPrefix; + global $wgDebugTimestamps, $wgRequestTime; if ( !$wgDebugRawPage && wfIsDebugRawPage() ) { return; } - // Turn $dest into a string if it's a boolean (for b/c) - if ( $dest === true ) { - $dest = 'all'; - } elseif ( $dest === false ) { - $dest = 'log'; - } + $text = trim( $text ); - $timer = wfDebugTimer(); - if ( $timer !== '' ) { - $text = preg_replace( '/[^\n]/', $timer . '\0', $text, 1 ); + // Inline logic from deprecated wfDebugTimer() + if ( $wgDebugTimestamps ) { + $context['seconds_elapsed'] = sprintf( + '%6.4f', + microtime( true ) - $wgRequestTime + ); + $context['memory_used'] = sprintf( + '%5.1fM', + ( memory_get_usage( true ) / ( 1024 * 1024 ) ) + ); } - if ( $dest === 'all' ) { - MWDebug::debugMsg( $text ); + if ( $wgDebugLogPrefix !== '' ) { + $context['prefix'] = $wgDebugLogPrefix; } - if ( $wgDebugLogFile != '' ) { - # Strip unprintables; they can switch terminal modes when binary data - # gets dumped, which is pretty annoying. - $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text ); - $text = $wgDebugLogPrefix . $text; - wfErrorLog( $text, $wgDebugLogFile ); - } + $logger = LoggerFactory::getInstance( 'wfDebug' ); + $logger->debug( $text, $context ); } /** @@ -1016,11 +1082,14 @@ function wfIsDebugRawPage() { /** * Get microsecond timestamps for debug logs * + * @deprecated since 1.25 * @return string */ function wfDebugTimer() { global $wgDebugTimestamps, $wgRequestTime; + wfDeprecated( __METHOD__, '1.25' ); + if ( !$wgDebugTimestamps ) { return ''; } @@ -1046,30 +1115,34 @@ function wfDebugMem( $exact = false ) { } /** - * Send a line to a supplementary debug log file, if configured, or main debug log if not. - * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to a string - * filename or an associative array mapping 'destination' to the desired filename. The - * associative array may also contain a 'sample' key with an integer value, specifying - * a sampling factor. + * Send a line to a supplementary debug log file, if configured, or main debug + * log if not. + * + * To configure a supplementary log file, set $wgDebugLogGroups[$logGroup] to + * a string filename or an associative array mapping 'destination' to the + * desired filename. The associative array may also contain a 'sample' key + * with an integer value, specifying a sampling factor. Sampled log events + * will be emitted with a 1 in N random chance. * * @since 1.23 support for sampling log messages via $wgDebugLogGroups. + * @since 1.25 support for additional context data + * @since 1.25 sample behavior dependent on configured $wgMWLoggerDefaultSpi * * @param string $logGroup * @param string $text * @param string|bool $dest Destination of the message: * - 'all': both to the log and HTML (debug toolbar or HTML comments) * - 'log': only to the log and not in HTML - * - 'private': only to the specifc log if set in $wgDebugLogGroups and + * - 'private': only to the specific log if set in $wgDebugLogGroups and * discarded otherwise * For backward compatibility, it can also take a boolean: * - true: same as 'all' * - false: same as 'private' + * @param array $context Additional logging context data */ -function wfDebugLog( $logGroup, $text, $dest = 'all' ) { - global $wgDebugLogGroups; - - $text = trim( $text ) . "\n"; - +function wfDebugLog( + $logGroup, $text, $dest = 'all', array $context = array() +) { // Turn $dest into a string if it's a boolean (for b/c) if ( $dest === true ) { $dest = 'all'; @@ -1077,66 +1150,24 @@ function wfDebugLog( $logGroup, $text, $dest = 'all' ) { $dest = 'private'; } - if ( !isset( $wgDebugLogGroups[$logGroup] ) ) { - if ( $dest !== 'private' ) { - wfDebug( "[$logGroup] $text", $dest ); - } - return; - } - - if ( $dest === 'all' ) { - MWDebug::debugMsg( "[$logGroup] $text" ); - } - - $logConfig = $wgDebugLogGroups[$logGroup]; - if ( $logConfig === false ) { - return; - } - if ( is_array( $logConfig ) ) { - if ( isset( $logConfig['sample'] ) && mt_rand( 1, $logConfig['sample'] ) !== 1 ) { - return; - } - $destination = $logConfig['destination']; - } else { - $destination = strval( $logConfig ); - } + $text = trim( $text ); - $time = wfTimestamp( TS_DB ); - $wiki = wfWikiID(); - $host = wfHostname(); - wfErrorLog( "$time $host $wiki: $text", $destination ); + $logger = LoggerFactory::getInstance( $logGroup ); + $context['private'] = ( $dest === 'private' ); + $logger->info( $text, $context ); } /** * Log for database errors * + * @since 1.25 support for additional context data + * * @param string $text Database error message. + * @param array $context Additional logging context data */ -function wfLogDBError( $text ) { - global $wgDBerrorLog, $wgDBerrorLogTZ; - static $logDBErrorTimeZoneObject = null; - - if ( $wgDBerrorLog ) { - $host = wfHostname(); - $wiki = wfWikiID(); - - if ( $wgDBerrorLogTZ && !$logDBErrorTimeZoneObject ) { - $logDBErrorTimeZoneObject = new DateTimeZone( $wgDBerrorLogTZ ); - } - - // Workaround for https://bugs.php.net/bug.php?id=52063 - // Can be removed when min PHP > 5.3.2 - if ( $logDBErrorTimeZoneObject === null ) { - $d = date_create( "now" ); - } else { - $d = date_create( "now", $logDBErrorTimeZoneObject ); - } - - $date = $d->format( 'D M j G:i:s T Y' ); - - $text = "$date\t$host\t$wiki\t" . trim( $text ) . "\n"; - wfErrorLog( $text, $wgDBerrorLog ); - } +function wfLogDBError( $text, array $context = array() ) { + $logger = LoggerFactory::getInstance( 'wfLogDBError' ); + $logger->error( trim( $text ), $context ); } /** @@ -1188,138 +1219,93 @@ function wfLogWarning( $msg, $callerOffset = 1, $level = E_USER_WARNING ) { * * Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will * send lines to the specified port, prefixed by the specified prefix and a space. + * @since 1.25 support for additional context data * * @param string $text * @param string $file Filename + * @param array $context Additional logging context data * @throws MWException + * @deprecated since 1.25 Use MediaWiki\Logger\LegacyLogger::emit or UDPTransport */ -function wfErrorLog( $text, $file ) { - if ( substr( $file, 0, 4 ) == 'udp:' ) { - # Needs the sockets extension - if ( preg_match( '!^(tcp|udp):(?://)?\[([0-9a-fA-F:]+)\]:(\d+)(?:/(.*))?$!', $file, $m ) ) { - // IPv6 bracketed host - $host = $m[2]; - $port = intval( $m[3] ); - $prefix = isset( $m[4] ) ? $m[4] : false; - $domain = AF_INET6; - } elseif ( preg_match( '!^(tcp|udp):(?://)?([a-zA-Z0-9.-]+):(\d+)(?:/(.*))?$!', $file, $m ) ) { - $host = $m[2]; - if ( !IP::isIPv4( $host ) ) { - $host = gethostbyname( $host ); - } - $port = intval( $m[3] ); - $prefix = isset( $m[4] ) ? $m[4] : false; - $domain = AF_INET; - } else { - throw new MWException( __METHOD__ . ': Invalid UDP specification' ); - } - - // Clean it up for the multiplexer - if ( strval( $prefix ) !== '' ) { - $text = preg_replace( '/^/m', $prefix . ' ', $text ); - - // Limit to 64KB - if ( strlen( $text ) > 65506 ) { - $text = substr( $text, 0, 65506 ); - } - - if ( substr( $text, -1 ) != "\n" ) { - $text .= "\n"; - } - } elseif ( strlen( $text ) > 65507 ) { - $text = substr( $text, 0, 65507 ); - } - - $sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP ); - if ( !$sock ) { - return; - } - - socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port ); - socket_close( $sock ); - } else { - wfSuppressWarnings(); - $exists = file_exists( $file ); - $size = $exists ? filesize( $file ) : false; - if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) { - file_put_contents( $file, $text, FILE_APPEND ); - } - wfRestoreWarnings(); - } +function wfErrorLog( $text, $file, array $context = array() ) { + wfDeprecated( __METHOD__, '1.25' ); + $logger = LoggerFactory::getInstance( 'wfErrorLog' ); + $context['destination'] = $file; + $logger->info( trim( $text ), $context ); } /** * @todo document */ function wfLogProfilingData() { - global $wgRequestTime, $wgDebugLogFile, $wgDebugLogGroups, $wgDebugRawPage; - global $wgProfileLimit, $wgUser, $wgRequest; + global $wgDebugLogGroups, $wgDebugRawPage; - StatCounter::singleton()->flush(); + $context = RequestContext::getMain(); + $request = $context->getRequest(); $profiler = Profiler::instance(); + $profiler->setContext( $context ); + $profiler->logData(); - # Profiling must actually be enabled... - if ( $profiler->isStub() ) { - return; + $config = $context->getConfig(); + if ( $config->has( 'StatsdServer' ) ) { + $statsdServer = explode( ':', $config->get( 'StatsdServer' ) ); + $statsdHost = $statsdServer[0]; + $statsdPort = isset( $statsdServer[1] ) ? $statsdServer[1] : 8125; + $statsdSender = new SocketSender( $statsdHost, $statsdPort ); + $statsdClient = new StatsdClient( $statsdSender ); + $statsdClient->send( $context->getStats()->getBuffer() ); } - // Get total page request time and only show pages that longer than - // $wgProfileLimit time (default is 0) - $elapsed = microtime( true ) - $wgRequestTime; - if ( $elapsed <= $wgProfileLimit ) { + # Profiling must actually be enabled... + if ( $profiler instanceof ProfilerStub ) { return; } - $profiler->logData(); - - // Check whether this should be logged in the debug file. if ( isset( $wgDebugLogGroups['profileoutput'] ) && $wgDebugLogGroups['profileoutput'] === false ) { - // Explicitely disabled - return; - } - if ( !isset( $wgDebugLogGroups['profileoutput'] ) && $wgDebugLogFile == '' ) { - // Logging not enabled; no point going further + // Explicitly disabled return; } if ( !$wgDebugRawPage && wfIsDebugRawPage() ) { return; } - $forward = ''; + $ctx = array( 'elapsed' => $request->getElapsedTime() ); if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { - $forward = ' forwarded for ' . $_SERVER['HTTP_X_FORWARDED_FOR']; + $ctx['forwarded_for'] = $_SERVER['HTTP_X_FORWARDED_FOR']; } if ( !empty( $_SERVER['HTTP_CLIENT_IP'] ) ) { - $forward .= ' client IP ' . $_SERVER['HTTP_CLIENT_IP']; + $ctx['client_ip'] = $_SERVER['HTTP_CLIENT_IP']; } if ( !empty( $_SERVER['HTTP_FROM'] ) ) { - $forward .= ' from ' . $_SERVER['HTTP_FROM']; + $ctx['from'] = $_SERVER['HTTP_FROM']; } - if ( $forward ) { - $forward = "\t(proxied via {$_SERVER['REMOTE_ADDR']}{$forward})"; + if ( isset( $ctx['forwarded_for'] ) || + isset( $ctx['client_ip'] ) || + isset( $ctx['from'] ) ) { + $ctx['proxy'] = $_SERVER['REMOTE_ADDR']; } + // Don't load $wgUser at this late stage just for statistics purposes - // @todo FIXME: We can detect some anons even if it is not loaded. See User::getId() - if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) { - $forward .= ' anon'; - } + // @todo FIXME: We can detect some anons even if it is not loaded. + // See User::getId() + $user = $context->getUser(); + $ctx['anon'] = $user->isItemLoaded( 'id' ) && $user->isAnon(); // Command line script uses a FauxRequest object which does not have // any knowledge about an URL and throw an exception instead. try { - $requestUrl = $wgRequest->getRequestURL(); - } catch ( MWException $e ) { - $requestUrl = 'n/a'; + $ctx['url'] = urldecode( $request->getRequestURL() ); + } catch ( Exception $ignored ) { + // no-op } - $log = sprintf( "%s\t%04.3f\t%s\n", - gmdate( 'YmdHis' ), $elapsed, - urldecode( $requestUrl . $forward ) ); + $ctx['output'] = $profiler->getOutput(); - wfDebugLog( 'profileoutput', $log . $profiler->getOutput() ); + $log = LoggerFactory::getInstance( 'profileoutput' ); + $log->info( "Elapsed: {elapsed}; URL: <{url}>\n{output}", $ctx ); } /** @@ -1330,7 +1316,8 @@ function wfLogProfilingData() { * @return void */ function wfIncrStats( $key, $count = 1 ) { - StatCounter::singleton()->incr( $key, $count ); + $stats = RequestContext::getMain()->getStats(); + $stats->updateCount( $key, $count ); } /** @@ -1573,10 +1560,8 @@ function wfMsgForContentNoTrans( $key ) { function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform = true ) { wfDeprecated( __METHOD__, '1.21' ); - wfProfileIn( __METHOD__ ); $message = wfMsgGetKey( $key, $useDB, $forContent, $transform ); $message = wfMsgReplaceArgs( $message, $args ); - wfProfileOut( __METHOD__ ); return $message; } @@ -1595,7 +1580,7 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) { wfDeprecated( __METHOD__, '1.21' ); - wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) ); + Hooks::run( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) ); $cache = MessageCache::singleton(); $message = $cache->get( $key, $useDB, $langCode ); @@ -1710,15 +1695,15 @@ function wfMsgExt( $key, $options ) { array_shift( $args ); array_shift( $args ); $options = (array)$options; + $validOptions = array( 'parse', 'parseinline', 'escape', 'escapenoentities', 'replaceafter', + 'parsemag', 'content' ); foreach ( $options as $arrayKey => $option ) { if ( !preg_match( '/^[0-9]+|language$/', $arrayKey ) ) { - # An unknown index, neither numeric nor "language" + // An unknown index, neither numeric nor "language" wfWarn( "wfMsgExt called with incorrect parameter key $arrayKey", 1, E_USER_WARNING ); - } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, - array( 'parse', 'parseinline', 'escape', 'escapenoentities', - 'replaceafter', 'parsemag', 'content' ) ) ) { - # A numeric index with unknown value + } elseif ( preg_match( '/^[0-9]+$/', $arrayKey ) && !in_array( $option, $validOptions ) ) { + // A numeric index with unknown value wfWarn( "wfMsgExt called with incorrect parameter $option", 1, E_USER_WARNING ); } } @@ -1879,52 +1864,37 @@ function wfDebugBacktrace( $limit = 0 ) { /** * Get a debug backtrace as a string * + * @param bool|null $raw If true, the return value is plain text. If false, HTML. + * Defaults to $wgCommandLineMode if unset. * @return string + * @since 1.25 Supports $raw parameter. */ -function wfBacktrace() { +function wfBacktrace( $raw = null ) { global $wgCommandLineMode; - if ( $wgCommandLineMode ) { - $msg = ''; - } else { - $msg = "
    \n"; + if ( $raw === null ) { + $raw = $wgCommandLineMode; } - $backtrace = wfDebugBacktrace(); - foreach ( $backtrace as $call ) { - if ( isset( $call['file'] ) ) { - $f = explode( DIRECTORY_SEPARATOR, $call['file'] ); - $file = $f[count( $f ) - 1]; - } else { - $file = '-'; - } - if ( isset( $call['line'] ) ) { - $line = $call['line']; - } else { - $line = '-'; - } - if ( $wgCommandLineMode ) { - $msg .= "$file line $line calls "; - } else { - $msg .= '
  • ' . $file . ' line ' . $line . ' calls '; - } - if ( !empty( $call['class'] ) ) { - $msg .= $call['class'] . $call['type']; - } - $msg .= $call['function'] . '()'; - if ( $wgCommandLineMode ) { - $msg .= "\n"; - } else { - $msg .= "
  • \n"; - } - } - if ( $wgCommandLineMode ) { - $msg .= "\n"; + if ( $raw ) { + $frameFormat = "%s line %s calls %s()\n"; + $traceFormat = "%s"; } else { - $msg .= "
\n"; + $frameFormat = "
  • %s line %s calls %s()
  • \n"; + $traceFormat = "
      \n%s
    \n"; } - return $msg; + $frames = array_map( function ( $frame ) use ( $frameFormat ) { + $file = !empty( $frame['file'] ) ? basename( $frame['file'] ) : '-'; + $line = isset( $frame['line'] ) ? $frame['line'] : '-'; + $call = $frame['function']; + if ( !empty( $frame['class'] ) ) { + $call = $frame['class'] . $frame['type'] . $call; + } + return sprintf( $frameFormat, $file, $line, $call ); + }, wfDebugBacktrace() ); + + return sprintf( $traceFormat, implode( '', $frames ) ); } /** @@ -2148,10 +2118,12 @@ function wfVarDump( $var ) { */ function wfHttpError( $code, $label, $desc ) { global $wgOut; - $wgOut->disable(); header( "HTTP/1.0 $code $label" ); header( "Status: $code $label" ); - $wgOut->sendCacheControl(); + if ( $wgOut ) { + $wgOut->disable(); + $wgOut->sendCacheControl(); + } header( 'Content-type: text/html; charset=utf-8' ); print "" . @@ -2495,20 +2467,6 @@ function wfIsHHVM() { return defined( 'HHVM_VERSION' ); } -/** - * Swap two variables - * - * @deprecated since 1.24 - * @param mixed $x - * @param mixed $y - */ -function swap( &$x, &$y ) { - wfDeprecated( __FUNCTION__, '1.24' ); - $z = $x; - $x = $y; - $y = $z; -} - /** * Tries to get the system directory for temporary files. First * $wgTmpDirectory is checked, and then the TMPDIR, TMP, and TEMP @@ -2659,13 +2617,19 @@ function wfIniGetBool( $setting ) { * Also fixes the locale problems on Linux in PHP 5.2.6+ (bug backported to * earlier distro releases of PHP) * - * @param string $args,... + * @param string ... strings to escape and glue together, or a single array of strings parameter * @return string */ function wfEscapeShellArg( /*...*/ ) { wfInitShellLocale(); $args = func_get_args(); + if ( count( $args ) === 1 && is_array( reset( $args ) ) ) { + // If only one argument has been passed, and that argument is an array, + // treat it as a list of arguments + $args = reset( $args ); + } + $first = true; $retVal = ''; foreach ( $args as $arg ) { @@ -2756,6 +2720,8 @@ function wfShellExecDisabled() { * @param array $options Array of options: * - duplicateStderr: Set this to true to duplicate stderr to stdout, * including errors from limit.sh + * - profileMethod: By default this function will profile based on the calling + * method. Set this to a string for an alternative method to profile from * * @return string Collected stdout as a string */ @@ -2774,6 +2740,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), } $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr']; + $profileMethod = isset( $options['profileMethod'] ) ? $options['profileMethod'] : wfGetCaller(); wfInitShellLocale(); @@ -2795,12 +2762,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), } } if ( is_array( $cmd ) ) { - // Command line may be given as an array, escape each value and glue them together with a space - $cmdVals = array(); - foreach ( $cmd as $val ) { - $cmdVals[] = wfEscapeShellArg( $val ); - } - $cmd = implode( ' ', $cmdVals ); + $cmd = wfEscapeShellArg( $cmd ); } $cmd = $envcmd . $cmd; @@ -2847,6 +2809,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $desc[3] = array( 'pipe', 'w' ); } $pipes = null; + $scoped = Profiler::instance()->scopedProfileIn( __FUNCTION__ . '-' . $profileMethod ); $proc = proc_open( $cmd, $desc, $pipes ); if ( !$proc ) { wfDebugLog( 'exec', "proc_open() failed: $cmd" ); @@ -2986,7 +2949,9 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), * function, as all the arguments to wfShellExec can become unwieldy. * * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded. - * @param string $cmd Command line, properly escaped for shell. + * @param string|string[] $cmd If string, a properly shell-escaped command line, + * or an array of unescaped arguments, in which case each value will be escaped + * Example: [ 'convert', '-font', 'font name' ] would produce "'convert' '-font' 'font name'" * @param null|mixed &$retval Optional, will receive the program's exit code. * (non-zero is usually failure) * @param array $environ Optional environment variables which should be @@ -2996,7 +2961,8 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), * @return string Collected stdout and stderr as a string */ function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) { - return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) ); + return wfShellExec( $cmd, $retval, $environ, $limits, + array( 'duplicateStderr' => true, 'profileMethod' => wfGetCaller() ) ); } /** @@ -3016,15 +2982,6 @@ function wfInitShellLocale() { } } -/** - * Alias to wfShellWikiCmd() - * - * @see wfShellWikiCmd() - */ -function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) { - return wfShellWikiCmd( $script, $parameters, $options ); -} - /** * Generate a shell-escaped command line string to run a MediaWiki cli script. * Note that $parameters should be a flat array and an option with an argument @@ -3041,14 +2998,14 @@ function wfShellWikiCmd( $script, array $parameters = array(), array $options = global $wgPhpCli; // Give site config file a chance to run the script in a wrapper. // The caller may likely want to call wfBasename() on $script. - wfRunHooks( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) ); + Hooks::run( 'wfShellWikiCmd', array( &$script, &$parameters, &$options ) ); $cmd = isset( $options['php'] ) ? array( $options['php'] ) : array( $wgPhpCli ); if ( isset( $options['wrapper'] ) ) { $cmd[] = $options['wrapper']; } $cmd[] = $script; // Escape each parameter for shell - return implode( " ", array_map( 'wfEscapeShellArg', array_merge( $cmd, $parameters ) ) ); + return wfEscapeShellArg( array_merge( $cmd, $parameters ) ); } /** @@ -3093,10 +3050,8 @@ function wfMerge( $old, $mine, $yours, &$result ) { fclose( $yourtextFile ); # Check for a conflict - $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a --overlap-only ' . - wfEscapeShellArg( $mytextName ) . ' ' . - wfEscapeShellArg( $oldtextName ) . ' ' . - wfEscapeShellArg( $yourtextName ); + $cmd = wfEscapeShellArg( $wgDiff3, '-a', '--overlap-only', $mytextName, + $oldtextName, $yourtextName ); $handle = popen( $cmd, 'r' ); if ( fgets( $handle, 1024 ) ) { @@ -3107,8 +3062,8 @@ function wfMerge( $old, $mine, $yours, &$result ) { pclose( $handle ); # Merge differences - $cmd = wfEscapeShellArg( $wgDiff3 ) . ' -a -e --merge ' . - wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName ); + $cmd = wfEscapeShellArg( $wgDiff3, '-a', '-e', '--merge', $mytextName, + $oldtextName, $yourtextName ); $handle = popen( $cmd, 'r' ); $result = ''; do { @@ -3132,7 +3087,9 @@ function wfMerge( $old, $mine, $yours, &$result ) { /** * Returns unified plain-text diff of two texts. - * Useful for machine processing of diffs. + * "Useful" for machine processing of diffs. + * + * @deprecated since 1.25, use DiffEngine/UnifiedDiffFormatter directly * * @param string $before The text before the changes. * @param string $after The text after the changes. @@ -3172,6 +3129,11 @@ function wfDiff( $before, $after, $params = '-u' ) { $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName ); $h = popen( $cmd, 'r' ); + if ( !$h ) { + unlink( $oldtextName ); + unlink( $newtextName ); + throw new Exception( __METHOD__ . '(): popen() failed' ); + } $diff = ''; @@ -3493,7 +3455,7 @@ function wfResetSessionID() { $_SESSION = $tmp; } $newSessionId = session_id(); - wfRunHooks( 'ResetSessionID', array( $oldSessionId, $newSessionId ) ); + Hooks::run( 'ResetSessionID', array( $oldSessionId, $newSessionId ) ); } /** @@ -3628,7 +3590,7 @@ function wfSplitWikiID( $wiki ) { * * @return DatabaseBase */ -function &wfGetDB( $db, $groups = array(), $wiki = false ) { +function wfGetDB( $db, $groups = array(), $wiki = false ) { return wfGetLB( $wiki )->getConnection( $db, $groups, $wiki ); } @@ -3647,7 +3609,7 @@ function wfGetLB( $wiki = false ) { * * @return LBFactory */ -function &wfGetLBFactory() { +function wfGetLBFactory() { return LBFactory::singleton(); } @@ -3656,19 +3618,7 @@ function &wfGetLBFactory() { * Shortcut for RepoGroup::singleton()->findFile() * * @param string $title String or Title object - * @param array $options Associative array of options: - * time: requested time for an archived image, or false for the - * current version. An image object will be returned which was - * created at the specified time. - * - * ignoreRedirect: If true, do not follow file redirects - * - * private: If true, return restricted (deleted) files if the current - * user is allowed to view them. Otherwise, such files will not - * be found. - * - * bypassCache: If true, do not use the process-local cache of File objects - * + * @param array $options Associative array of options (see RepoGroup::findFile) * @return File|bool File, or false if the file does not exist */ function wfFindFile( $title, $options = array() ) { @@ -3763,46 +3713,79 @@ function wfGetNull() { } /** - * Modern version of wfWaitForSlaves(). Instead of looking at replication lag - * and waiting for it to go down, this waits for the slaves to catch up to the - * master position. Use this when updating very large numbers of rows, as - * in maintenance scripts, to avoid causing too much lag. Of course, this is - * a no-op if there are no slaves. + * Waits for the slaves to catch up to the master position + * + * Use this when updating very large numbers of rows, as in maintenance scripts, + * to avoid causing too much lag. Of course, this is a no-op if there are no slaves. + * + * By default this waits on the main DB cluster of the current wiki. + * If $cluster is set to "*" it will wait on all DB clusters, including + * external ones. If the lag being waiting on is caused by the code that + * does this check, it makes since to use $ifWritesSince, particularly if + * cluster is "*", to avoid excess overhead. + * + * Never call this function after a big DB write that is still in a transaction. + * This only makes sense after the possible lag inducing changes were committed. * * @param float|null $ifWritesSince Only wait if writes were done since this UNIX timestamp * @param string|bool $wiki Wiki identifier accepted by wfGetLB * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false. + * @param int|null $timeout Max wait time. Default: 1 day (cli), ~10 seconds (web) * @return bool Success (able to connect and no timeouts reached) */ -function wfWaitForSlaves( $ifWritesSince = false, $wiki = false, $cluster = false ) { +function wfWaitForSlaves( + $ifWritesSince = null, $wiki = false, $cluster = false, $timeout = null +) { // B/C: first argument used to be "max seconds of lag"; ignore such values - $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : false; + $ifWritesSince = ( $ifWritesSince > 1e9 ) ? $ifWritesSince : null; - if ( $cluster !== false ) { - $lb = wfGetLBFactory()->getExternalLB( $cluster ); - } else { - $lb = wfGetLB( $wiki ); + if ( $timeout === null ) { + $timeout = ( PHP_SAPI === 'cli' ) ? 86400 : 10; } - // bug 27975 - Don't try to wait for slaves if there are none - // Prevents permission error when getting master position - if ( $lb->getServerCount() > 1 ) { - if ( $ifWritesSince && !$lb->hasMasterConnection() ) { - return true; // assume no writes done - } - $dbw = $lb->getConnection( DB_MASTER, array(), $wiki ); - if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) { - return true; // no writes since the last wait + // Figure out which clusters need to be checked + $lbs = array(); + if ( $cluster === '*' ) { + wfGetLBFactory()->forEachLB( function ( LoadBalancer $lb ) use ( &$lbs ) { + $lbs[] = $lb; + } ); + } elseif ( $cluster !== false ) { + $lbs[] = wfGetLBFactory()->getExternalLB( $cluster ); + } else { + $lbs[] = wfGetLB( $wiki ); + } + + // Get all the master positions of applicable DBs right now. + // This can be faster since waiting on one cluster reduces the + // time needed to wait on the next clusters. + $masterPositions = array_fill( 0, count( $lbs ), false ); + foreach ( $lbs as $i => $lb ) { + // bug 27975 - Don't try to wait for slaves if there are none + // Prevents permission error when getting master position + if ( $lb->getServerCount() > 1 ) { + if ( $ifWritesSince && !$lb->hasMasterConnection() ) { + continue; // assume no writes done + } + // Use the empty string to not trigger selectDB() since the connection + // may have been to a server that does not have a DB for the current wiki. + $dbw = $lb->getConnection( DB_MASTER, array(), '' ); + if ( $ifWritesSince && $dbw->lastDoneWrites() < $ifWritesSince ) { + continue; // no writes since the last wait + } + $masterPositions[$i] = $dbw->getMasterPos(); } - $pos = $dbw->getMasterPos(); - // The DBMS may not support getMasterPos() or the whole - // load balancer might be fake (e.g. $wgAllDBsAreLocalhost). - if ( $pos !== false ) { - return $lb->waitForAll( $pos, PHP_SAPI === 'cli' ? 86400 : null ); + } + + $ok = true; + foreach ( $lbs as $i => $lb ) { + if ( $masterPositions[$i] ) { + // The DBMS may not support getMasterPos() or the whole + // load balancer might be fake (e.g. $wgAllDBsAreLocalhost). + $ok = $lb->waitForAll( $masterPositions[$i], $timeout ) && $ok; } } - return true; + return $ok; } /** @@ -3935,7 +3918,7 @@ function wfBCP47( $code ) { /** * Get a cache object. * - * @param int $inputType Cache type, one the the CACHE_* constants. + * @param int $inputType Cache type, one of the CACHE_* constants. * @return BagOStuff */ function wfGetCache( $inputType ) { @@ -3972,16 +3955,6 @@ function wfGetParserCacheStorage() { return ObjectCache::getInstance( $wgParserCacheType ); } -/** - * Get the cache object used by the language converter - * - * @return BagOStuff - */ -function wfGetLangConverterCacheStorage() { - global $wgLanguageConverterCacheType; - return ObjectCache::getInstance( $wgLanguageConverterCacheType ); -} - /** * Call hook functions defined in $wgHooks * @@ -3990,6 +3963,7 @@ function wfGetLangConverterCacheStorage() { * @param string|null $deprecatedVersion Optionally mark hook as deprecated with version number * * @return bool True if no handler aborted the hook + * @deprecated 1.25 - use Hooks::run */ function wfRunHooks( $event, array $args = array(), $deprecatedVersion = null ) { return Hooks::run( $event, $args, $deprecatedVersion ); @@ -4047,7 +4021,6 @@ function wfUnpack( $format, $data, $length = false ) { */ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { static $badImageCache = null; // based on bad_image_list msg - wfProfileIn( __METHOD__ ); # Handle redirects $redirectTitle = RepoGroup::singleton()->checkRedirect( Title::makeTitle( NS_FILE, $name ) ); @@ -4057,8 +4030,7 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { # Run the extension hook $bad = false; - if ( !wfRunHooks( 'BadImage', array( $name, &$bad ) ) ) { - wfProfileOut( __METHOD__ ); + if ( !Hooks::run( 'BadImage', array( $name, &$bad ) ) ) { return $bad; } @@ -4108,7 +4080,6 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { $contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false; $bad = isset( $badImages[$name] ) && !isset( $badImages[$name][$contextKey] ); - wfProfileOut( __METHOD__ ); return $bad; } @@ -4121,10 +4092,22 @@ function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) { */ function wfCanIPUseHTTPS( $ip ) { $canDo = true; - wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) ); + Hooks::run( 'CanIPUseHTTPS', array( $ip, &$canDo ) ); return !!$canDo; } +/** + * Determine input string is represents as infinity + * + * @param string $str The string to determine + * @return bool + * @since 1.25 + */ +function wfIsInfinity( $str ) { + $infinityValues = array( 'infinite', 'indefinite', 'infinity', 'never' ); + return in_array( $str, $infinityValues ); +} + /** * Work out the IP address based on various globals * For trusted proxies, use the XFF client IP (first of the chain) @@ -4148,6 +4131,7 @@ function wfGetIP() { * @return bool */ function wfIsTrustedProxy( $ip ) { + wfDeprecated( __METHOD__, '1.24' ); return IP::isTrustedProxy( $ip ); } @@ -4160,5 +4144,94 @@ function wfIsTrustedProxy( $ip ) { * @since 1.23 Supports CIDR ranges in $wgSquidServersNoPurge */ function wfIsConfiguredProxy( $ip ) { + wfDeprecated( __METHOD__, '1.24' ); return IP::isConfiguredProxy( $ip ); } + +/** + * Returns true if these thumbnail parameters match one that MediaWiki + * requests from file description pages and/or parser output. + * + * $params is considered non-standard if they involve a non-standard + * width or any non-default parameters aside from width and page number. + * The number of possible files with standard parameters is far less than + * that of all combinations; rate-limiting for them can thus be more generious. + * + * @param File $file + * @param array $params + * @return bool + * @since 1.24 Moved from thumb.php to GlobalFunctions in 1.25 + */ +function wfThumbIsStandard( File $file, array $params ) { + global $wgThumbLimits, $wgImageLimits, $wgResponsiveImages; + + $multipliers = array( 1 ); + if ( $wgResponsiveImages ) { + // These available sizes are hardcoded currently elsewhere in MediaWiki. + // @see Linker::processResponsiveImages + $multipliers[] = 1.5; + $multipliers[] = 2; + } + + $handler = $file->getHandler(); + if ( !$handler || !isset( $params['width'] ) ) { + return false; + } + + $basicParams = array(); + if ( isset( $params['page'] ) ) { + $basicParams['page'] = $params['page']; + } + + $thumbLimits = array(); + $imageLimits = array(); + // Expand limits to account for multipliers + foreach ( $multipliers as $multiplier ) { + $thumbLimits = array_merge( $thumbLimits, array_map( + function ( $width ) use ( $multiplier ) { + return round( $width * $multiplier ); + }, $wgThumbLimits ) + ); + $imageLimits = array_merge( $imageLimits, array_map( + function ( $pair ) use ( $multiplier ) { + return array( + round( $pair[0] * $multiplier ), + round( $pair[1] * $multiplier ), + ); + }, $wgImageLimits ) + ); + } + + // Check if the width matches one of $wgThumbLimits + if ( in_array( $params['width'], $thumbLimits ) ) { + $normalParams = $basicParams + array( 'width' => $params['width'] ); + // Append any default values to the map (e.g. "lossy", "lossless", ...) + $handler->normaliseParams( $file, $normalParams ); + } else { + // If not, then check if the width matchs one of $wgImageLimits + $match = false; + foreach ( $imageLimits as $pair ) { + $normalParams = $basicParams + array( 'width' => $pair[0], 'height' => $pair[1] ); + // Decide whether the thumbnail should be scaled on width or height. + // Also append any default values to the map (e.g. "lossy", "lossless", ...) + $handler->normaliseParams( $file, $normalParams ); + // Check if this standard thumbnail size maps to the given width + if ( $normalParams['width'] == $params['width'] ) { + $match = true; + break; + } + } + if ( !$match ) { + return false; // not standard for description pages + } + } + + // Check that the given values for non-page, non-width, params are just defaults + foreach ( $params as $key => $value ) { + if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) { + return false; + } + } + + return true; +} diff --git a/includes/Hooks.php b/includes/Hooks.php index 29287483..dffc7bcf 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -127,14 +127,17 @@ class Hooks { * @param string|null $deprecatedVersion Optionally, mark hook as deprecated with version number * @return bool True if no handler aborted the hook * + * @throws Exception + * @throws FatalError + * @throws MWException * @since 1.22 A hook function is not required to return a value for * processing to continue. Not returning a value (or explicitly * returning null) is equivalent to returning true. - * @throws MWException - * @throws FatalError */ public static function run( $event, array $args = array(), $deprecatedVersion = null ) { - wfProfileIn( 'hook: ' . $event ); + $profiler = Profiler::instance(); + $eventPS = $profiler->scopedProfileIn( 'hook: ' . $event ); + foreach ( self::getHandlers( $event ) as $hook ) { // Turn non-array values into an array. (Can't use casting because of objects.) if ( !is_array( $hook ) ) { @@ -179,7 +182,7 @@ class Hooks { // Run autoloader (workaround for call_user_func_array bug) // and throw error if not callable. if ( !is_callable( $callback ) ) { - throw new MWException( 'Invalid callback in hooks for ' . $event . "\n" ); + throw new MWException( 'Invalid callback ' . $func . ' in hooks for ' . $event . "\n" ); } /* @@ -193,8 +196,8 @@ class Hooks { $badhookmsg = null; $hook_args = array_merge( $hook, $args ); - // Profile first in case the Profiler causes errors. - wfProfileIn( $func ); + // Profile first in case the Profiler causes errors + $funcPS = $profiler->scopedProfileIn( $func ); set_error_handler( 'Hooks::hookErrorHandler' ); // mark hook as deprecated, if deprecation version is specified @@ -210,8 +213,9 @@ class Hooks { restore_error_handler(); throw $e; } + restore_error_handler(); - wfProfileOut( $func ); + $profiler->scopedProfileOut( $funcPS ); // Process the return value. if ( is_string( $retval ) ) { @@ -224,13 +228,11 @@ class Hooks { "Hook $func has invalid call signature; " . $badhookmsg ); } elseif ( $retval === false ) { - wfProfileOut( 'hook: ' . $event ); // False was returned. Stop processing, but no error. return false; } } - wfProfileOut( 'hook: ' . $event ); return true; } diff --git a/includes/Html.php b/includes/Html.php index 2e148140..d312e0a6 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -101,6 +101,35 @@ class Html { 'itemscope', ); + /** + * Modifies a set of attributes meant for button elements + * and apply a set of default attributes when $wgUseMediaWikiUIEverywhere enabled. + * @param array $attrs + * @param string[] $modifiers to add to the button + * @see https://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers + * @return array $attrs A modified attribute array + */ + public static function buttonAttributes( $attrs, $modifiers = array() ) { + global $wgUseMediaWikiUIEverywhere; + if ( $wgUseMediaWikiUIEverywhere ) { + if ( isset( $attrs['class'] ) ) { + if ( is_array( $attrs['class'] ) ) { + $attrs['class'][] = 'mw-ui-button'; + $attrs = array_merge( $attrs, $modifiers ); + // ensure compatibility with Xml + $attrs['class'] = implode( ' ', $attrs['class'] ); + } else { + $attrs['class'] .= ' mw-ui-button ' . implode( ' ', $modifiers ); + } + } else { + $attrs['class'] = array( 'mw-ui-button' ); + // ensure compatibility with Xml + $attrs['class'] = implode( ' ', array_merge( $attrs['class'], $modifiers ) ); + } + } + return $attrs; + } + /** * Modifies a set of attributes meant for text input elements * and apply a set of default attributes. @@ -113,28 +142,63 @@ class Html { if ( !$attrs ) { $attrs = array(); } - if ( isset( $attrs['class'] ) ) { - if ( is_array( $attrs['class'] ) ) { - $attrs['class'][] = 'mw-ui-input'; + if ( $wgUseMediaWikiUIEverywhere ) { + if ( isset( $attrs['class'] ) ) { + if ( is_array( $attrs['class'] ) ) { + $attrs['class'][] = 'mw-ui-input'; + } else { + $attrs['class'] .= ' mw-ui-input'; + } } else { - $attrs['class'] .= ' mw-ui-input'; + $attrs['class'] = 'mw-ui-input'; } - } else { - $attrs['class'] = 'mw-ui-input'; - } - if ( $wgUseMediaWikiUIEverywhere ) { - // Note that size can effect the desired width rendering of mw-ui-input elements - // so it is removed. Left intact when mediawiki ui not enabled. - unset( $attrs['size'] ); } return $attrs; } + /** + * Returns an HTML link element in a string styled as a button + * (when $wgUseMediaWikiUIEverywhere is enabled). + * + * @param string $contents The raw HTML contents of the element: *not* + * escaped! + * @param array $attrs Associative array of attributes, e.g., array( + * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for + * further documentation. + * @param string[] $modifiers to add to the button + * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers + * @return string Raw HTML + */ + public static function linkButton( $contents, $attrs, $modifiers = array() ) { + return self::element( 'a', + self::buttonAttributes( $attrs, $modifiers ), + $contents + ); + } + + /** + * Returns an HTML link element in a string styled as a button + * (when $wgUseMediaWikiUIEverywhere is enabled). + * + * @param string $contents The raw HTML contents of the element: *not* + * escaped! + * @param array $attrs Associative array of attributes, e.g., array( + * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for + * further documentation. + * @param string[] $modifiers to add to the button + * @see http://tools.wmflabs.org/styleguide/desktop/index.html for guidance on available modifiers + * @return string Raw HTML + */ + public static function submitButton( $contents, $attrs, $modifiers = array() ) { + $attrs['type'] = 'submit'; + $attrs['value'] = $contents; + return self::element( 'input', self::buttonAttributes( $attrs, $modifiers ) ); + } + /** * Returns an HTML element in a string. The major advantage here over * manually typing out the HTML is that it will escape all attribute - * values. If you're hardcoding all the attributes, or there are none, you - * should probably just type out the html element yourself. + * values. * * This is quite similar to Xml::tags(), but it implements some useful * HTML-specific logic. For instance, there is no $allowShortTag @@ -193,20 +257,11 @@ class Html { * @return string */ public static function openElement( $element, $attribs = array() ) { - global $wgWellFormedXml; $attribs = (array)$attribs; // This is not required in HTML5, but let's do it anyway, for // consistency and better compression. $element = strtolower( $element ); - // In text/html, initial and tags can be omitted under - // pretty much any sane circumstances, if they have no attributes. See: - // - if ( !$wgWellFormedXml && !$attribs - && in_array( $element, array( 'html', 'head' ) ) ) { - return ''; - } - // Remove invalid input types if ( $element == 'input' ) { $validTypes = array( @@ -236,8 +291,7 @@ class Html { 'tel', 'color', ); - if ( isset( $attribs['type'] ) - && !in_array( $attribs['type'], $validTypes ) ) { + if ( isset( $attribs['type'] ) && !in_array( $attribs['type'], $validTypes ) ) { unset( $attribs['type'] ); } } @@ -331,8 +385,9 @@ class Html { } // Simple checks using $attribDefaults - if ( isset( $attribDefaults[$element][$lcattrib] ) && - $attribDefaults[$element][$lcattrib] == $value ) { + if ( isset( $attribDefaults[$element][$lcattrib] ) + && $attribDefaults[$element][$lcattrib] == $value + ) { unset( $attribs[$attrib] ); } @@ -342,8 +397,9 @@ class Html { } // More subtle checks - if ( $element === 'link' && isset( $attribs['type'] ) - && strval( $attribs['type'] ) == 'text/css' ) { + if ( $element === 'link' + && isset( $attribs['type'] ) && strval( $attribs['type'] ) == 'text/css' + ) { unset( $attribs['type'] ); } if ( $element === 'input' ) { @@ -442,8 +498,7 @@ class Html { // For boolean attributes, support array( 'foo' ) instead of // requiring array( 'foo' => 'meaningless' ). - if ( is_int( $key ) - && in_array( strtolower( $value ), self::$boolAttribs ) ) { + if ( is_int( $key ) && in_array( strtolower( $value ), self::$boolAttribs ) ) { $key = $value; } @@ -522,14 +577,13 @@ class Html { // marks omitted, but not all. (Although a literal " is not // permitted, we don't check for that, since it will be escaped // anyway.) - # + // See also research done on further characters that need to be // escaped: http://code.google.com/p/html5lib/issues/detail?id=93 $badChars = "\\x00- '=<>`/\x{00a0}\x{1680}\x{180e}\x{180F}\x{2000}\x{2001}" . "\x{2002}\x{2003}\x{2004}\x{2005}\x{2006}\x{2007}\x{2008}\x{2009}" . "\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}"; - if ( $wgWellFormedXml || $value === '' - || preg_match( "![$badChars]!u", $value ) ) { + if ( $wgWellFormedXml || $value === '' || preg_match( "![$badChars]!u", $value ) ) { $quote = '"'; } else { $quote = ''; @@ -654,7 +708,7 @@ class Html { * new HTML5 input types and attributes. * * @param string $name Name attribute - * @param array $value Value attribute + * @param string $value Value attribute * @param string $type Type attribute * @param array $attribs Associative array of miscellaneous extra * attributes, passed to Html::element() @@ -665,7 +719,7 @@ class Html { $attribs['value'] = $value; $attribs['name'] = $name; if ( in_array( $type, array( 'text', 'search', 'email', 'password', 'number' ) ) ) { - $attribs = Html::getTextInputAttributes( $attribs ); + $attribs = self::getTextInputAttributes( $attribs ); } return self::element( 'input', $attribs ); } @@ -676,7 +730,7 @@ class Html { * @param string $name Name attribute * @param bool $checked Whether the checkbox is checked or not * @param array $attribs Array of additional attributes - * @return string + * @return string Raw HTML */ public static function check( $name, $checked = false, array $attribs = array() ) { if ( isset( $attribs['value'] ) ) { @@ -699,7 +753,7 @@ class Html { * @param string $name Name attribute * @param bool $checked Whether the checkbox is checked or not * @param array $attribs Array of additional attributes - * @return string + * @return string Raw HTML */ public static function radio( $name, $checked = false, array $attribs = array() ) { if ( isset( $attribs['value'] ) ) { @@ -722,7 +776,7 @@ class Html { * @param string $label Contents of the label * @param string $id ID of the element being labeled * @param array $attribs Additional attributes - * @return string + * @return string Raw HTML */ public static function label( $label, $id, array $attribs = array() ) { $attribs += array( @@ -768,7 +822,7 @@ class Html { } else { $spacedValue = $value; } - return self::element( 'textarea', Html::getTextInputAttributes( $attribs ), $spacedValue ); + return self::element( 'textarea', self::getTextInputAttributes( $attribs ), $spacedValue ); } /** @@ -833,13 +887,13 @@ class Html { continue; } if ( $nsId === NS_MAIN ) { - // For other namespaces use use the namespace prefix as label, but for + // For other namespaces use the namespace prefix as label, but for // main we don't use "" but the user message describing it (e.g. "(Main)" or "(Article)") $nsName = wfMessage( 'blanknamespace' )->text(); } elseif ( is_int( $nsId ) ) { $nsName = $wgContLang->convertNamespace( $nsId ); } - $optionsHtml[] = Html::element( + $optionsHtml[] = self::element( 'option', array( 'disabled' => in_array( $nsId, $params['disable'] ), 'value' => $nsId, @@ -858,7 +912,7 @@ class Html { $ret = ''; if ( isset( $params['label'] ) ) { - $ret .= Html::element( + $ret .= self::element( 'label', array( 'for' => isset( $selectAttribs['id'] ) ? $selectAttribs['id'] : null, ), $params['label'] @@ -866,11 +920,11 @@ class Html { } // Wrap options in a element with a list of change tags that can be + * applied by users. + * + * @param array $selectedTags The tags that should be preselected in the + * list. Any tags in this list, but not in the list returned by + * ChangeTags::listExplicitlyDefinedTags, will be appended to the + * @return array HTML
    diff --git a/includes/title/ForeignTitle.php b/includes/title/ForeignTitle.php new file mode 100644 index 00000000..ed96d17c --- /dev/null +++ b/includes/title/ForeignTitle.php @@ -0,0 +1,117 @@ +namespaceId = null; + } else { + $this->namespaceId = intval( $namespaceId ); + } + $this->namespaceName = str_replace( ' ', '_', $namespaceName ); + $this->pageName = str_replace( ' ', '_', $pageName ); + } + + /** + * Do we know the namespace ID of the page on the foreign wiki? + * @return bool + */ + public function isNamespaceIdKnown() { + return !is_null( $this->namespaceId ); + } + + /** + * @return int + * @throws MWException If isNamespaceIdKnown() is false, it does not make + * sense to call this function. + */ + public function getNamespaceId() { + if ( is_null( $this->namespaceId ) ) { + throw new MWException( + "Attempted to call getNamespaceId when the namespace ID is not known" ); + } + return $this->namespaceId; + } + + /** @return string */ + public function getNamespaceName() { + return $this->namespaceName; + } + + /** @return string */ + public function getText() { + return $this->pageName; + } + + /** @return string */ + public function getFullText() { + $result = ''; + if ( $this->namespaceName ) { + $result .= $this->namespaceName . ':'; + } + $result .= $this->pageName; + return $result; + } + + /** + * Returns a string representation of the title, for logging. This is purely + * informative and must not be used programmatically. Use the appropriate + * ImportTitleFactory to generate the correct string representation for a + * given use. + * + * @return string + */ + public function __toString() { + $name = ''; + if ( $this->isNamespaceIdKnown() ) { + $name .= '{ns' . $this->namespaceId . '}'; + } else { + $name .= '{ns??}'; + } + $name .= $this->namespaceName . ':' . $this->pageName; + + return $name; + } +} diff --git a/includes/title/ForeignTitleFactory.php b/includes/title/ForeignTitleFactory.php new file mode 100644 index 00000000..427afdf3 --- /dev/null +++ b/includes/title/ForeignTitleFactory.php @@ -0,0 +1,36 @@ + and attributes found in an XML dump. + * + * @param string $title The page title + * @param int|null $ns The namespace ID, or null if this data is not available + * @return ForeignTitle + */ + public function createForeignTitle( $title, $ns = null ); +} diff --git a/includes/title/ImportTitleFactory.php b/includes/title/ImportTitleFactory.php new file mode 100644 index 00000000..629616d8 --- /dev/null +++ b/includes/title/ImportTitleFactory.php @@ -0,0 +1,36 @@ + and attributes found in an XML dump. + * + * Although exported XML dumps have contained a map of namespace IDs to names + * since MW 1.5, the importer used to completely ignore the tag + * before MW 1.25. It is therefore possible that custom XML dumps (i.e. not + * generated by Special:Export) have been created without this metadata. + * As a result, this code falls back to using namespace data for the local + * wiki (similar to buggy pre-1.25 behaviour) if $ns is not supplied. + * + * @param string $title The page title + * @param int|null $ns The namespace ID, or null if this data is not available + * @return ForeignTitle + */ + public function createForeignTitle( $title, $ns = null ) { + $pieces = explode( ':', $title, 2 ); + + global $wgContLang; + + // Can we assume that the part of the page title before the colon is a + // namespace name? + // + // XML export schema version 0.5 and earlier (MW 1.18 and earlier) does not + // contain a tag, so we need to be able to handle that case. + // + // If we know the namespace ID, we assume a non-zero namespace ID means + // the ':' sets off a valid namespace name. If we don't know the namespace + // ID, we fall back to using the local wiki's namespace names to resolve + // this -- better than nothing, and mimics the old crappy behavior + $isNamespacePartValid = is_null( $ns ) ? + ( $wgContLang->getNsIndex( $pieces[0] ) !== false ) : + $ns != 0; + + if ( count( $pieces ) === 2 && $isNamespacePartValid ) { + list( $namespaceName, $pageName ) = $pieces; + } else { + $namespaceName = ''; + $pageName = $title; + } + + return new ForeignTitle( $ns, $namespaceName, $pageName ); + } +} diff --git a/includes/title/NaiveImportTitleFactory.php b/includes/title/NaiveImportTitleFactory.php new file mode 100644 index 00000000..43c662e7 --- /dev/null +++ b/includes/title/NaiveImportTitleFactory.php @@ -0,0 +1,65 @@ + 100, we look for a local namespace with + * a matching namespace name. If that can't be found, we dump the page in the + * main namespace as a last resort. + */ +class NaiveImportTitleFactory implements ImportTitleFactory { + /** + * Determines which local title best corresponds to the given foreign title. + * If such a title can't be found or would be locally invalid, null is + * returned. + * + * @param ForeignTitle $foreignTitle The ForeignTitle to convert + * @return Title|null + */ + public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) { + global $wgContLang; + + if ( $foreignTitle->isNamespaceIdKnown() ) { + $foreignNs = $foreignTitle->getNamespaceId(); + + // For built-in namespaces (0 <= ID < 100), we try to find a local NS with + // the same namespace ID + if ( $foreignNs < 100 && MWNamespace::exists( $foreignNs ) ) { + return Title::makeTitleSafe( $foreignNs, $foreignTitle->getText() ); + } + } + + // Do we have a local namespace by the same name as the foreign + // namespace? + $targetNs = $wgContLang->getNsIndex( $foreignTitle->getNamespaceName() ); + if ( $targetNs !== false ) { + return Title::makeTitleSafe( $targetNs, $foreignTitle->getText() ); + } + + // Otherwise, just fall back to main namespace + return Title::makeTitleSafe( 0, $foreignTitle->getFullText() ); + } +} diff --git a/includes/title/NamespaceAwareForeignTitleFactory.php b/includes/title/NamespaceAwareForeignTitleFactory.php new file mode 100644 index 00000000..bf97e2cd --- /dev/null +++ b/includes/title/NamespaceAwareForeignTitleFactory.php @@ -0,0 +1,134 @@ + 'name' which contains + * the complete namespace setup of the foreign wiki. Such data could be + * obtained from siteinfo/namespaces in an XML dump file, or by an action API + * query such as api.php?action=query&meta=siteinfo&siprop=namespaces. If + * this data is unavailable, use NaiveForeignTitleFactory instead. + */ + public function __construct( $foreignNamespaces ) { + $this->foreignNamespaces = $foreignNamespaces; + if ( !is_null( $foreignNamespaces ) ) { + $this->foreignNamespacesFlipped = array(); + foreach ( $foreignNamespaces as $id => $name ) { + $newKey = self::normalizeNamespaceName( $name ); + $this->foreignNamespacesFlipped[$newKey] = $id; + } + } + } + + /** + * Creates a ForeignTitle object based on the page title, and optionally the + * namespace ID, of a page on a foreign wiki. These values could be, for + * example, the and <ns> attributes found in an XML dump. + * + * @param string $title The page title + * @param int|null $ns The namespace ID, or null if this data is not available + * @return ForeignTitle + */ + public function createForeignTitle( $title, $ns = null ) { + // Export schema version 0.5 and earlier (MW 1.18 and earlier) does not + // contain a <ns> tag, so we need to be able to handle that case. + if ( is_null( $ns ) ) { + return self::parseTitleNoNs( $title ); + } else { + return self::parseTitleWithNs( $title, $ns ); + } + } + + /** + * Helper function to parse the title when the namespace ID is not specified. + * + * @param string $title + * @return ForeignTitle + */ + protected function parseTitleNoNs( $title ) { + $pieces = explode( ':', $title, 2 ); + $key = self::normalizeNamespaceName( $pieces[0] ); + + // Does the part before the colon match a known namespace? Check the + // foreign namespaces + $isNamespacePartValid = isset( $this->foreignNamespacesFlipped[$key] ); + + if ( count( $pieces ) === 2 && $isNamespacePartValid ) { + list( $namespaceName, $pageName ) = $pieces; + $ns = $this->foreignNamespacesFlipped[$key]; + } else { + $namespaceName = ''; + $pageName = $title; + $ns = 0; + } + + return new ForeignTitle( $ns, $namespaceName, $pageName ); + } + + /** + * Helper function to parse the title when the namespace value is known. + * + * @param string $title + * @param int $ns + * @return ForeignTitle + */ + protected function parseTitleWithNs( $title, $ns ) { + $pieces = explode( ':', $title, 2 ); + + if ( isset( $this->foreignNamespaces[$ns] ) ) { + $namespaceName = $this->foreignNamespaces[$ns]; + } else { + $namespaceName = $ns == '0' ? '' : $pieces[0]; + } + + // We assume that the portion of the page title before the colon is the + // namespace name, except in the case of namespace 0 + if ( $ns != '0' ) { + $pageName = $pieces[1]; + } else { + $pageName = $title; + } + + return new ForeignTitle( $ns, $namespaceName, $pageName ); + } +} diff --git a/includes/title/NamespaceImportTitleFactory.php b/includes/title/NamespaceImportTitleFactory.php new file mode 100644 index 00000000..0c1d0c40 --- /dev/null +++ b/includes/title/NamespaceImportTitleFactory.php @@ -0,0 +1,52 @@ +<?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 + * + * @file + * @license GPL 2+ + */ + +/** + * A class to convert page titles on a foreign wiki (ForeignTitle objects) into + * page titles on the local wiki (Title objects), placing all pages in a fixed + * local namespace. + */ +class NamespaceImportTitleFactory implements ImportTitleFactory { + /** @var int */ + protected $ns; + + /** + * @param int $ns The namespace to use for all pages + */ + public function __construct( $ns ) { + if ( !MWNamespace::exists( $ns ) ) { + throw new MWException( "Namespace $ns doesn't exist on this wiki" ); + } + $this->ns = $ns; + } + + /** + * Determines which local title best corresponds to the given foreign title. + * If such a title can't be found or would be locally invalid, null is + * returned. + * + * @param ForeignTitle $foreignTitle The ForeignTitle to convert + * @return Title|null + */ + public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) { + return Title::makeTitleSafe( $this->ns, $foreignTitle->getText() ); + } +} diff --git a/includes/title/PageLinkRenderer.php b/includes/title/PageLinkRenderer.php index fb1096e0..ca91f583 100644 --- a/includes/title/PageLinkRenderer.php +++ b/includes/title/PageLinkRenderer.php @@ -29,6 +29,7 @@ * URLs, and how links are encoded in a given output format. * * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue + * @since 1.23 */ interface PageLinkRenderer { /** diff --git a/includes/title/SubpageImportTitleFactory.php b/includes/title/SubpageImportTitleFactory.php new file mode 100644 index 00000000..b0be7afa --- /dev/null +++ b/includes/title/SubpageImportTitleFactory.php @@ -0,0 +1,55 @@ +<?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 + * + * @file + * @license GPL 2+ + */ + +/** + * A class to convert page titles on a foreign wiki (ForeignTitle objects) into + * page titles on the local wiki (Title objects), placing all pages as subpages + * of a given root page. + */ +class SubpageImportTitleFactory implements ImportTitleFactory { + /** @var Title */ + protected $rootPage; + + /** + * @param Title $rootPage The root page under which all pages should be + * created + */ + public function __construct( Title $rootPage ) { + if ( !MWNamespace::hasSubpages( $rootPage->getNamespace() ) ) { + throw new MWException( "The root page you specified, $rootPage, is in a " . + "namespace where subpages are not allowed" ); + } + $this->rootPage = $rootPage; + } + + /** + * Determines which local title best corresponds to the given foreign title. + * If such a title can't be found or would be locally invalid, null is + * returned. + * + * @param ForeignTitle $foreignTitle The ForeignTitle to convert + * @return Title|null + */ + public function createTitleFromForeignTitle( ForeignTitle $foreignTitle ) { + return Title::newFromText( $this->rootPage->getPrefixedDBkey() . '/' . + $foreignTitle->getFullText() ); + } +} diff --git a/includes/title/TitleFormatter.php b/includes/title/TitleFormatter.php index 7c71ef5e..aad83769 100644 --- a/includes/title/TitleFormatter.php +++ b/includes/title/TitleFormatter.php @@ -29,6 +29,7 @@ * forms to be used in the database, in urls, in wikitext, etc. * * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue + * @since 1.23 */ interface TitleFormatter { /** diff --git a/includes/title/TitleParser.php b/includes/title/TitleParser.php index 0635ee86..381b1d09 100644 --- a/includes/title/TitleParser.php +++ b/includes/title/TitleParser.php @@ -29,6 +29,7 @@ * forms to be used in the database, in urls, in wikitext, etc. * * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue + * @since 1.23 */ interface TitleParser { /** diff --git a/includes/title/TitleValue.php b/includes/title/TitleValue.php index 402247c2..5cac3470 100644 --- a/includes/title/TitleValue.php +++ b/includes/title/TitleValue.php @@ -32,6 +32,7 @@ * It does not represent a link, and does not support interwiki prefixes etc. * * @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue + * @since 1.23 */ class TitleValue { /** diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index 14959c26..6da8250b 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -69,8 +69,6 @@ abstract class UploadBase { const WINDOWS_NONASCII_FILENAME = 13; const FILENAME_TOO_LONG = 14; - const SESSION_STATUS_KEY = 'wsUploadStatusData'; - /** * @param int $error * @return string @@ -152,7 +150,7 @@ abstract class UploadBase { // Give hooks the chance to handle this request $className = null; - wfRunHooks( 'UploadCreateFromRequest', array( $type, &$className ) ); + Hooks::run( 'UploadCreateFromRequest', array( $type, &$className ) ); if ( is_null( $className ) ) { $className = 'UploadFrom' . $type; wfDebug( __METHOD__ . ": class name: $className\n" ); @@ -263,7 +261,6 @@ abstract class UploadBase { * @return string|bool The real path if it was a virtual URL Returns false on failure */ function getRealPath( $srcPath ) { - wfProfileIn( __METHOD__ ); $repo = RepoGroup::singleton()->getLocalRepo(); if ( $repo->isVirtualUrl( $srcPath ) ) { /** @todo Just make uploads work with storage paths UploadFromStash @@ -277,7 +274,6 @@ abstract class UploadBase { } else { $path = $srcPath; } - wfProfileOut( __METHOD__ ); return $path; } @@ -287,13 +283,11 @@ abstract class UploadBase { * @return mixed Const self::OK or else an array with error information */ public function verifyUpload() { - wfProfileIn( __METHOD__ ); /** * If there was no filename or a zero size given, give up quick. */ if ( $this->isEmptyFile() ) { - wfProfileOut( __METHOD__ ); return array( 'status' => self::EMPTY_FILE ); } @@ -303,7 +297,6 @@ abstract class UploadBase { */ $maxSize = self::getMaxUploadSize( $this->getSourceType() ); if ( $this->mFileSize > $maxSize ) { - wfProfileOut( __METHOD__ ); return array( 'status' => self::FILE_TOO_LARGE, @@ -318,7 +311,6 @@ abstract class UploadBase { */ $verification = $this->verifyFile(); if ( $verification !== true ) { - wfProfileOut( __METHOD__ ); return array( 'status' => self::VERIFICATION_ERROR, @@ -331,22 +323,18 @@ abstract class UploadBase { */ $result = $this->validateName(); if ( $result !== true ) { - wfProfileOut( __METHOD__ ); return $result; } $error = ''; - if ( !wfRunHooks( 'UploadVerification', + if ( !Hooks::run( 'UploadVerification', array( $this->mDestName, $this->mTempPath, &$error ) ) ) { - wfProfileOut( __METHOD__ ); return array( 'status' => self::HOOK_ABORTED, 'error' => $error ); } - wfProfileOut( __METHOD__ ); - return array( 'status' => self::OK ); } @@ -388,12 +376,10 @@ abstract class UploadBase { */ protected function verifyMimeType( $mime ) { global $wgVerifyMimeType; - wfProfileIn( __METHOD__ ); if ( $wgVerifyMimeType ) { wfDebug( "mime: <$mime> extension: <{$this->mFinalExtension}>\n" ); global $wgMimeTypeBlacklist; if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { - wfProfileOut( __METHOD__ ); return array( 'filetype-badmime', $mime ); } @@ -408,15 +394,12 @@ abstract class UploadBase { $ieTypes = $magic->getIEMimeTypes( $this->mTempPath, $chunk, $extMime ); foreach ( $ieTypes as $ieType ) { if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) { - wfProfileOut( __METHOD__ ); return array( 'filetype-bad-ie-mime', $ieType ); } } } - wfProfileOut( __METHOD__ ); - return true; } @@ -426,12 +409,10 @@ abstract class UploadBase { * @return mixed True of the file is verified, array otherwise. */ protected function verifyFile() { - global $wgVerifyMimeType; - wfProfileIn( __METHOD__ ); + global $wgVerifyMimeType, $wgDisableUploadScriptChecks; $status = $this->verifyPartialFile(); if ( $status !== true ) { - wfProfileOut( __METHOD__ ); return $status; } @@ -442,32 +423,39 @@ abstract class UploadBase { if ( $wgVerifyMimeType ) { # XXX: Missing extension will be caught by validateName() via getTitle() if ( $this->mFinalExtension != '' && !$this->verifyExtension( $mime, $this->mFinalExtension ) ) { - wfProfileOut( __METHOD__ ); return array( 'filetype-mime-mismatch', $this->mFinalExtension, $mime ); } } + # check for htmlish code and javascript + if ( !$wgDisableUploadScriptChecks ) { + if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { + $svgStatus = $this->detectScriptInSvg( $this->mTempPath, false ); + if ( $svgStatus !== false ) { + + return $svgStatus; + } + } + } + $handler = MediaHandler::getHandler( $mime ); if ( $handler ) { $handlerStatus = $handler->verifyUpload( $this->mTempPath ); if ( !$handlerStatus->isOK() ) { $errors = $handlerStatus->getErrorsArray(); - wfProfileOut( __METHOD__ ); return reset( $errors ); } } - wfRunHooks( 'UploadVerifyFile', array( $this, $mime, &$status ) ); + Hooks::run( 'UploadVerifyFile', array( $this, $mime, &$status ) ); if ( $status !== true ) { - wfProfileOut( __METHOD__ ); return $status; } wfDebug( __METHOD__ . ": all clear; passing.\n" ); - wfProfileOut( __METHOD__ ); return true; } @@ -482,7 +470,6 @@ abstract class UploadBase { */ protected function verifyPartialFile() { global $wgAllowJavaUploads, $wgDisableUploadScriptChecks; - wfProfileIn( __METHOD__ ); # getTitle() sets some internal parameters like $this->mFinalExtension $this->getTitle(); @@ -493,7 +480,6 @@ abstract class UploadBase { $mime = $this->mFileProps['file-mime']; $status = $this->verifyMimeType( $mime ); if ( $status !== true ) { - wfProfileOut( __METHOD__ ); return $status; } @@ -501,14 +487,12 @@ abstract class UploadBase { # check for htmlish code and javascript if ( !$wgDisableUploadScriptChecks ) { if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) { - wfProfileOut( __METHOD__ ); return array( 'uploadscripted' ); } if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { - $svgStatus = $this->detectScriptInSvg( $this->mTempPath ); + $svgStatus = $this->detectScriptInSvg( $this->mTempPath, true ); if ( $svgStatus !== false ) { - wfProfileOut( __METHOD__ ); return $svgStatus; } @@ -525,13 +509,11 @@ abstract class UploadBase { $errors = $zipStatus->getErrorsArray(); $error = reset( $errors ); if ( $error[0] !== 'zip-wrong-format' ) { - wfProfileOut( __METHOD__ ); return $error; } } if ( $this->mJavaDetected ) { - wfProfileOut( __METHOD__ ); return array( 'uploadjava' ); } @@ -540,13 +522,10 @@ abstract class UploadBase { # Scan the uploaded file for viruses $virus = $this->detectVirus( $this->mTempPath ); if ( $virus ) { - wfProfileOut( __METHOD__ ); return array( 'uploadvirus', $virus ); } - wfProfileOut( __METHOD__ ); - return true; } @@ -639,11 +618,11 @@ abstract class UploadBase { */ public function checkWarnings() { global $wgLang; - wfProfileIn( __METHOD__ ); $warnings = array(); $localFile = $this->getLocalFile(); + $localFile->load( File::READ_LATEST ); $filename = $localFile->getName(); /** @@ -699,17 +678,15 @@ abstract class UploadBase { } // Check dupes against archives - $archivedImage = new ArchivedFile( null, 0, "{$hash}.{$this->mFinalExtension}" ); - if ( $archivedImage->getID() > 0 ) { - if ( $archivedImage->userCan( File::DELETED_FILE ) ) { - $warnings['duplicate-archive'] = $archivedImage->getName(); + $archivedFile = new ArchivedFile( null, 0, '', $hash ); + if ( $archivedFile->getID() > 0 ) { + if ( $archivedFile->userCan( File::DELETED_FILE ) ) { + $warnings['duplicate-archive'] = $archivedFile->getName(); } else { $warnings['duplicate-archive'] = ''; } } - wfProfileOut( __METHOD__ ); - return $warnings; } @@ -725,7 +702,7 @@ abstract class UploadBase { * @return Status Indicating the whether the upload succeeded. */ public function performUpload( $comment, $pageText, $watch, $user ) { - wfProfileIn( __METHOD__ ); + $this->getLocalFile()->load( File::READ_LATEST ); $status = $this->getLocalFile()->upload( $this->mTempPath, @@ -745,14 +722,43 @@ abstract class UploadBase { WatchedItem::IGNORE_USER_RIGHTS ); } - wfRunHooks( 'UploadComplete', array( &$this ) ); - } + Hooks::run( 'UploadComplete', array( &$this ) ); - wfProfileOut( __METHOD__ ); + $this->postProcessUpload(); + } return $status; } + /** + * Perform extra steps after a successful upload. + * + * @since 1.25 + */ + public function postProcessUpload() { + global $wgUploadThumbnailRenderMap; + + $jobs = array(); + + $sizes = $wgUploadThumbnailRenderMap; + rsort( $sizes ); + + $file = $this->getLocalFile(); + + foreach ( $sizes as $size ) { + if ( $file->isVectorized() + || $file->getWidth() > $size ) { + $jobs[] = new ThumbnailRenderJob( $file->getTitle(), array( + 'transformParams' => array( 'width' => $size ), + ) ); + } + } + + if ( $jobs ) { + JobQueueGroup::singleton()->push( $jobs ); + } + } + /** * Returns the title of the file to be uploaded. Sets mTitleError in case * the name was illegal. @@ -911,14 +917,11 @@ abstract class UploadBase { */ public function stashFile( User $user = null ) { // was stashSessionFile - wfProfileIn( __METHOD__ ); $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash( $user ); $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() ); $this->mLocalFile = $file; - wfProfileOut( __METHOD__ ); - return $file; } @@ -1058,7 +1061,6 @@ abstract class UploadBase { */ public static function detectScript( $file, $mime, $extension ) { global $wgAllowTitlesInSVG; - wfProfileIn( __METHOD__ ); # ugly hack: for text files, always look at the entire file. # For binary field, just check the first K. @@ -1074,7 +1076,6 @@ abstract class UploadBase { $chunk = strtolower( $chunk ); if ( !$chunk ) { - wfProfileOut( __METHOD__ ); return false; } @@ -1099,7 +1100,6 @@ abstract class UploadBase { # check for HTML doctype if ( preg_match( "/<!DOCTYPE *X?HTML/i", $chunk ) ) { - wfProfileOut( __METHOD__ ); return true; } @@ -1108,7 +1108,6 @@ abstract class UploadBase { // PHP/expat will interpret the given encoding in the xml declaration (bug 47304) if ( $extension == 'svg' || strpos( $mime, 'image/svg' ) === 0 ) { if ( self::checkXMLEncodingMissmatch( $file ) ) { - wfProfileOut( __METHOD__ ); return true; } @@ -1147,7 +1146,6 @@ abstract class UploadBase { foreach ( $tags as $tag ) { if ( false !== strpos( $chunk, $tag ) ) { wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" ); - wfProfileOut( __METHOD__ ); return true; } @@ -1163,7 +1161,6 @@ abstract class UploadBase { # look for script-types if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) { wfDebug( __METHOD__ . ": found script types\n" ); - wfProfileOut( __METHOD__ ); return true; } @@ -1171,7 +1168,6 @@ abstract class UploadBase { # look for html-style script-urls if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) { wfDebug( __METHOD__ . ": found html-style script urls\n" ); - wfProfileOut( __METHOD__ ); return true; } @@ -1179,13 +1175,11 @@ abstract class UploadBase { # look for css-style script-urls if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) { wfDebug( __METHOD__ . ": found css-style script urls\n" ); - wfProfileOut( __METHOD__ ); return true; } wfDebug( __METHOD__ . ": no scripts found\n" ); - wfProfileOut( __METHOD__ ); return false; } @@ -1252,9 +1246,10 @@ abstract class UploadBase { /** * @param string $filename + * @param bool $partial * @return mixed False of the file is verified (does not contain scripts), array otherwise. */ - protected function detectScriptInSvg( $filename ) { + protected function detectScriptInSvg( $filename, $partial ) { $this->mSVGNSError = false; $check = new XmlTypeCheck( $filename, @@ -1264,7 +1259,8 @@ abstract class UploadBase { ); if ( $check->wellFormed !== true ) { // Invalid xml (bug 58553) - return array( 'uploadinvalidxml' ); + // But only when non-partial (bug 65724) + return $partial ? false : array( 'uploadinvalidxml' ); } elseif ( $check->filterMatch ) { if ( $this->mSVGNSError ) { return array( 'uploadscriptednamespace', $this->mSVGNSError ); @@ -1602,11 +1598,9 @@ abstract class UploadBase { */ public static function detectVirus( $file ) { global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut; - wfProfileIn( __METHOD__ ); if ( !$wgAntivirus ) { wfDebug( __METHOD__ . ": virus scanner disabled\n" ); - wfProfileOut( __METHOD__ ); return null; } @@ -1615,7 +1609,6 @@ abstract class UploadBase { wfDebug( __METHOD__ . ": unknown virus scanner: $wgAntivirus\n" ); $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", array( 'virus-badscanner', $wgAntivirus ) ); - wfProfileOut( __METHOD__ ); return wfMessage( 'virus-unknownscanner' )->text() . " $wgAntivirus"; } @@ -1689,8 +1682,6 @@ abstract class UploadBase { wfDebug( __METHOD__ . ": FOUND VIRUS! scanner feedback: $output \n" ); } - wfProfileOut( __METHOD__ ); - return $output; } @@ -1705,6 +1696,7 @@ abstract class UploadBase { private function checkOverwrite( $user ) { // First check whether the local file can be overwritten $file = $this->getLocalFile(); + $file->load( File::READ_LATEST ); if ( $file->exists() ) { if ( !self::userCanReUpload( $user, $file ) ) { return array( 'fileexists-forbidden', $file->getName() ); @@ -1716,7 +1708,7 @@ abstract class UploadBase { /* Check shared conflicts: if the local file does not exist, but * wfFindFile finds a file, it exists in a shared repository. */ - $file = wfFindFile( $this->getTitle() ); + $file = wfFindFile( $this->getTitle(), array( 'latest' => true ) ); if ( $file && !$user->isAllowed( 'reupload-shared' ) ) { return array( 'fileexists-shared-forbidden', $file->getName() ); } @@ -1745,6 +1737,8 @@ abstract class UploadBase { return false; } + $img->load( File::READ_LATEST ); + return $user->getId() == $img->getUser( 'id' ); } @@ -1950,29 +1944,38 @@ abstract class UploadBase { } /** - * Get the current status of a chunked upload (used for polling). - * The status will be read from the *current* user session. + * Get the current status of a chunked upload (used for polling) + * + * The value will be read from cache. + * + * @param User $user * @param string $statusKey * @return Status[]|bool */ - public static function getSessionStatus( $statusKey ) { - return isset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] ) - ? $_SESSION[self::SESSION_STATUS_KEY][$statusKey] - : false; + public static function getSessionStatus( User $user, $statusKey ) { + $key = wfMemcKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey ); + + return wfGetCache( CACHE_ANYTHING )->get( $key ); } /** - * Set the current status of a chunked upload (used for polling). - * The status will be stored in the *current* user session. + * Set the current status of a chunked upload (used for polling) + * + * The value will be set in cache for 1 day + * + * @param User $user * @param string $statusKey * @param array|bool $value * @return void */ - public static function setSessionStatus( $statusKey, $value ) { + public static function setSessionStatus( User $user, $statusKey, $value ) { + $key = wfMemcKey( 'uploadstatus', $user->getId() ?: md5( $user->getName() ), $statusKey ); + + $cache = wfGetCache( CACHE_ANYTHING ); if ( $value === false ) { - unset( $_SESSION[self::SESSION_STATUS_KEY][$statusKey] ); + $cache->delete( $key ); } else { - $_SESSION[self::SESSION_STATUS_KEY][$statusKey] = $value; + $cache->set( $key, $value, 86400 ); } } } diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php index 14993023..cc9f5c85 100644 --- a/includes/upload/UploadFromChunks.php +++ b/includes/upload/UploadFromChunks.php @@ -77,7 +77,7 @@ class UploadFromChunks extends UploadFromFile { $this->verifyChunk(); // Create a local stash target - $this->mLocalFile = parent::stashFile(); + $this->mLocalFile = parent::stashFile( $user ); // Update the initial file offset (based on file size) $this->mOffset = $this->mLocalFile->getSize(); $this->mFileKey = $this->mLocalFile->getFileKey(); @@ -170,20 +170,6 @@ class UploadFromChunks extends UploadFromFile { return $status; } - /** - * Perform the upload, then remove the temp copy afterward - * @param string $comment - * @param string $pageText - * @param bool $watch - * @param User $user - * @return Status - */ - public function performUpload( $comment, $pageText, $watch, $user ) { - $rv = parent::performUpload( $comment, $pageText, $watch, $user ); - - return $rv; - } - /** * Returns the virtual chunk location: * @param int $index @@ -303,10 +289,10 @@ class UploadFromChunks extends UploadFromFile { } /** - * Gets the current offset in fromt the stashedupload table + * Get the offset at which the next uploaded chunk will be appended to * @return int Current byte offset of the chunk file set */ - private function getOffset() { + public function getOffset() { if ( $this->mOffset !== null ) { return $this->mOffset; } diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php index b6056401..fc59ace5 100644 --- a/includes/upload/UploadFromUrl.php +++ b/includes/upload/UploadFromUrl.php @@ -118,7 +118,7 @@ class UploadFromUrl extends UploadBase { public static function isAllowedUrl( $url ) { if ( !isset( self::$allowedUrls[$url] ) ) { $allowed = true; - wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) ); + Hooks::run( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) ); self::$allowedUrls[$url] = $allowed; } @@ -231,12 +231,18 @@ class UploadFromUrl extends UploadBase { * @return int Number of bytes handled */ public function saveTempFileChunk( $req, $buffer ) { + wfDebugLog( 'fileupload', 'Received chunk of ' . strlen( $buffer ) . ' bytes' ); $nbytes = fwrite( $this->mTmpHandle, $buffer ); if ( $nbytes == strlen( $buffer ) ) { $this->mFileSize += $nbytes; } else { // Well... that's not good! + wfDebugLog( + 'fileupload', + 'Short write ' . $this->nbytes . '/' . strlen( $buffer ) . + ' bytes, aborting with ' . $this->mFileSize . ' uploaded so far' + ); fclose( $this->mTmpHandle ); $this->mTmpHandle = false; } @@ -262,6 +268,7 @@ class UploadFromUrl extends UploadBase { if ( !$this->mTmpHandle ) { return Status::newFatal( 'tmp-create-error' ); } + wfDebugLog( 'fileupload', 'Temporary file created "' . $this->mTempPath . '"' ); $this->mRemoveTempFile = true; $this->mFileSize = 0; @@ -275,7 +282,12 @@ class UploadFromUrl extends UploadBase { if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) { $options['timeout'] = $wgCopyUploadTimeout; } - $req = MWHttpRequest::factory( $this->mUrl, $options ); + wfDebugLog( + 'fileupload', + 'Starting download from "' . $this->mUrl . '" ' . + '<' . implode( ',', array_keys( array_filter( $options ) ) ) . '>' + ); + $req = MWHttpRequest::factory( $this->mUrl, $options, __METHOD__ ); $req->setCallback( array( $this, 'saveTempFileChunk' ) ); $status = $req->execute(); @@ -288,8 +300,14 @@ class UploadFromUrl extends UploadBase { return Status::newFatal( 'tmp-write-error' ); } - if ( !$status->isOk() ) { - return $status; + wfDebugLog( 'fileupload', $status ); + if ( $status->isOk() ) { + wfDebugLog( 'fileupload', 'Download by URL completed successfuly.' ); + } else { + wfDebugLog( + 'fileupload', + 'Download by URL completed with HTTP status ' . $req->getStatus() + ); } return $status; diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php index 7d80b448..c07665a0 100644 --- a/includes/upload/UploadStash.php +++ b/includes/upload/UploadStash.php @@ -151,6 +151,7 @@ class UploadStash { if ( !$this->files[$key]->exists() ) { wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" ); + // @todo Is this not an UploadStashFileNotFoundException case? throw new UploadStashBadPathException( "path doesn't exist" ); } @@ -726,9 +727,6 @@ class UploadStashFile extends UnregisteredLocalFile { class UploadStashException extends MWException { } -class UploadStashNotAvailableException extends UploadStashException { -} - class UploadStashFileNotFoundException extends UploadStashException { } diff --git a/includes/utils/ArrayUtils.php b/includes/utils/ArrayUtils.php deleted file mode 100644 index 1e521cb8..00000000 --- a/includes/utils/ArrayUtils.php +++ /dev/null @@ -1,187 +0,0 @@ -<?php -/** - * Methods to play with arrays. - * - * 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 - */ - -/** - * A collection of static methods to play with arrays. - * - * @since 1.21 - */ -class ArrayUtils { - /** - * Sort the given array in a pseudo-random order which depends only on the - * given key and each element value. This is typically used for load - * balancing between servers each with a local cache. - * - * Keys are preserved. The input array is modified in place. - * - * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small - * strings, md5() is only 10% slower than hash('joaat',...) etc., - * since the function call overhead dominates. So there's not much - * justification for breaking compatibility with installations - * compiled with ./configure --disable-hash. - * - * @param array $array Array to sort - * @param string $key - * @param string $separator A separator used to delimit the array elements and the - * key. This can be chosen to provide backwards compatibility with - * various consistent hash implementations that existed before this - * function was introduced. - */ - public static function consistentHashSort( &$array, $key, $separator = "\000" ) { - $hashes = array(); - foreach ( $array as $elt ) { - $hashes[$elt] = md5( $elt . $separator . $key ); - } - uasort( $array, function ( $a, $b ) use ( $hashes ) { - return strcmp( $hashes[$a], $hashes[$b] ); - } ); - } - - /** - * Given an array of non-normalised probabilities, this function will select - * an element and return the appropriate key - * - * @param array $weights - * @return bool|int|string - */ - public static function pickRandom( $weights ) { - if ( !is_array( $weights ) || count( $weights ) == 0 ) { - return false; - } - - $sum = array_sum( $weights ); - if ( $sum == 0 ) { - # No loads on any of them - # In previous versions, this triggered an unweighted random selection, - # but this feature has been removed as of April 2006 to allow for strict - # separation of query groups. - return false; - } - $max = mt_getrandmax(); - $rand = mt_rand( 0, $max ) / $max * $sum; - - $sum = 0; - foreach ( $weights as $i => $w ) { - $sum += $w; - # Do not return keys if they have 0 weight. - # Note that the "all 0 weight" case is handed above - if ( $w > 0 && $sum >= $rand ) { - break; - } - } - - return $i; - } - - /** - * Do a binary search, and return the index of the largest item that sorts - * less than or equal to the target value. - * - * @since 1.23 - * - * @param array $valueCallback A function to call to get the value with - * a given array index. - * @param int $valueCount The number of items accessible via $valueCallback, - * indexed from 0 to $valueCount - 1 - * @param array $comparisonCallback A callback to compare two values, returning - * -1, 0 or 1 in the style of strcmp(). - * @param string $target The target value to find. - * - * @return int|bool The item index of the lower bound, or false if the target value - * sorts before all items. - */ - public static function findLowerBound( $valueCallback, $valueCount, - $comparisonCallback, $target - ) { - if ( $valueCount === 0 ) { - return false; - } - - $min = 0; - $max = $valueCount; - do { - $mid = $min + ( ( $max - $min ) >> 1 ); - $item = call_user_func( $valueCallback, $mid ); - $comparison = call_user_func( $comparisonCallback, $target, $item ); - if ( $comparison > 0 ) { - $min = $mid; - } elseif ( $comparison == 0 ) { - $min = $mid; - break; - } else { - $max = $mid; - } - } while ( $min < $max - 1 ); - - if ( $min == 0 ) { - $item = call_user_func( $valueCallback, $min ); - $comparison = call_user_func( $comparisonCallback, $target, $item ); - if ( $comparison < 0 ) { - // Before the first item - return false; - } - } - return $min; - } - - /** - * Do array_diff_assoc() on multi-dimensional arrays. - * - * Note: empty arrays are removed. - * - * @since 1.23 - * - * @param array $array1 The array to compare from - * @param array $array2,... More arrays to compare against - * @return array An array containing all the values from array1 - * that are not present in any of the other arrays. - */ - public static function arrayDiffAssocRecursive( $array1 ) { - $arrays = func_get_args(); - array_shift( $arrays ); - $ret = array(); - - foreach ( $array1 as $key => $value ) { - if ( is_array( $value ) ) { - $args = array( $value ); - foreach ( $arrays as $array ) { - if ( isset( $array[$key] ) ) { - $args[] = $array[$key]; - } - } - $valueret = call_user_func_array( __METHOD__, $args ); - if ( count( $valueret ) ) { - $ret[$key] = $valueret; - } - } else { - foreach ( $arrays as $array ) { - if ( isset( $array[$key] ) && $array[$key] === $value ) { - continue 2; - } - } - $ret[$key] = $value; - } - } - - return $ret; - } -} diff --git a/includes/utils/AutoloadGenerator.php b/includes/utils/AutoloadGenerator.php new file mode 100644 index 00000000..9cf8cab5 --- /dev/null +++ b/includes/utils/AutoloadGenerator.php @@ -0,0 +1,296 @@ +<?php + +/** + * Accepts a list of files and directories to search for + * php files and generates $wgAutoloadLocalClasses or $wgAutoloadClasses + * lines for all detected classes. These lines are written out + * to an autoload.php file in the projects provided basedir. + * + * Usage: + * + * $gen = new AutoloadGenerator( __DIR__ ); + * $gen->readDir( __DIR__ . '/includes' ); + * $gen->readFile( __DIR__ . '/foo.php' ) + * $gen->generateAutoload(); + */ +class AutoloadGenerator { + /** + * @var string Root path of the project being scanned for classes + */ + protected $basepath; + + /** + * @var ClassCollector Helper class extracts class names from php files + */ + protected $collector; + + /** + * @var array Map of file shortpath to list of FQCN detected within file + */ + protected $classes = array(); + + /** + * @var string The global variable to write output to + */ + protected $variableName = 'wgAutoloadClasses'; + + /** + * @var array Map of FQCN to relative path(from self::$basepath) + */ + protected $overrides = array(); + + /** + * @param string $basepath Root path of the project being scanned for classes + * @param array|string $flags + * + * local - If this flag is set $wgAutoloadLocalClasses will be build instead + * of $wgAutoloadClasses + */ + public function __construct( $basepath, $flags = array() ) { + if ( !is_array( $flags ) ) { + $flags = array( $flags ); + } + $this->basepath = self::normalizePathSeparator( realpath( $basepath ) ); + $this->collector = new ClassCollector; + if ( in_array( 'local', $flags ) ) { + $this->variableName = 'wgAutoloadLocalClasses'; + } + } + + /** + * Force a class to be autoloaded from a specific path, regardless of where + * or if it was detected. + * + * @param string $fqcn FQCN to force the location of + * @param string $inputPath Full path to the file containing the class + * @throws Exception + */ + public function forceClassPath( $fqcn, $inputPath ) { + $path = self::normalizePathSeparator( realpath( $inputPath ) ); + if ( !$path ) { + throw new \Exception( "Invalid path: $inputPath" ); + } + $len = strlen( $this->basepath ); + if ( substr( $path, 0, $len ) !== $this->basepath ) { + throw new \Exception( "Path is not within basepath: $inputPath" ); + } + $shortpath = substr( $path, $len ); + $this->overrides[$fqcn] = $shortpath; + } + + /** + * @param string $inputPath Path to a php file to find classes within + * @throws Exception + */ + public function readFile( $inputPath ) { + // NOTE: do NOT expand $inputPath using realpath(). It is perfectly + // reasonable for LocalSettings.php and similiar files to be symlinks + // to files that are outside of $this->basepath. + $inputPath = self::normalizePathSeparator( $inputPath ); + $len = strlen( $this->basepath ); + if ( substr( $inputPath, 0, $len ) !== $this->basepath ) { + throw new \Exception( "Path is not within basepath: $inputPath" ); + } + $result = $this->collector->getClasses( + file_get_contents( $inputPath ) + ); + if ( $result ) { + $shortpath = substr( $inputPath, $len ); + $this->classes[$shortpath] = $result; + } + } + + /** + * @param string $dir Path to a directory to recursively search + * for php files with either .php or .inc extensions + */ + public function readDir( $dir ) { + $it = new RecursiveDirectoryIterator( + self::normalizePathSeparator( realpath( $dir ) ) ); + $it = new RecursiveIteratorIterator( $it ); + + foreach ( $it as $path => $file ) { + $ext = pathinfo( $path, PATHINFO_EXTENSION ); + // some older files in mw use .inc + if ( $ext === 'php' || $ext === 'inc' ) { + $this->readFile( $path ); + } + } + } + + /** + * Write out all known classes to autoload.php in + * the provided basedir + * + * @param string $commandName Value used in file comment to direct + * developers towards the appropriate way to update the autoload. + */ + public function generateAutoload( $commandName = 'AutoloadGenerator' ) { + $content = array(); + + // We need to generate a line each rather than exporting the + // full array so __DIR__ can be prepended to all the paths + $format = "%s => __DIR__ . %s,"; + foreach ( $this->classes as $path => $contained ) { + $exportedPath = var_export( $path, true ); + foreach ( $contained as $fqcn ) { + $content[$fqcn] = sprintf( + $format, + var_export( $fqcn, true ), + $exportedPath + ); + } + } + + foreach ( $this->overrides as $fqcn => $path ) { + $content[$fqcn] = sprintf( + $format, + var_export( $fqcn, true ), + var_export( $path, true ) + ); + } + + // sort for stable output + ksort( $content ); + + // extensions using this generator are appending to the existing + // autoload. + if ( $this->variableName === 'wgAutoloadClasses' ) { + $op = '+='; + } else { + $op = '='; + } + + $output = implode( "\n\t", $content ); + file_put_contents( + $this->basepath . '/autoload.php', + <<<EOD +<?php +// This file is generated by $commandName, do not adjust manually +// @codingStandardsIgnoreFile +global \${$this->variableName}; + +\${$this->variableName} {$op} array( + {$output} +); + +EOD + ); + } + + /** + * Ensure that Unix-style path separators ("/") are used in the path. + * + * @param string $path + * @return string + */ + protected static function normalizePathSeparator( $path ) { + return str_replace( '\\', '/', $path ); + } +} + +/** + * Reads PHP code and returns the FQCN of every class defined within it. + */ +class ClassCollector { + + /** + * @var string Current namespace + */ + protected $namespace = ''; + + /** + * @var array List of FQCN detected in this pass + */ + protected $classes; + + /** + * @var array Token from token_get_all() that started an expect sequence + */ + protected $startToken; + + /** + * @var array List of tokens that are members of the current expect sequence + */ + protected $tokens; + + /** + * @var string $code PHP code (including <?php) to detect class names from + * @return array List of FQCN detected within the tokens + */ + public function getClasses( $code ) { + $this->namespace = ''; + $this->classes = array(); + $this->startToken = null; + $this->tokens = array(); + + foreach ( token_get_all( $code ) as $token ) { + if ( $this->startToken === null ) { + $this->tryBeginExpect( $token ); + } else { + $this->tryEndExpect( $token ); + } + } + + return $this->classes; + } + + /** + * Determine if $token begins the next expect sequence. + * + * @param array $token + */ + protected function tryBeginExpect( $token ) { + if ( is_string( $token ) ) { + return; + } + switch ( $token[0] ) { + case T_NAMESPACE: + case T_CLASS: + case T_INTERFACE: + $this->startToken = $token; + } + } + + /** + * Accepts the next token in an expect sequence + * + * @param array + */ + protected function tryEndExpect( $token ) { + switch ( $this->startToken[0] ) { + case T_NAMESPACE: + if ( $token === ';' || $token === '{' ) { + $this->namespace = $this->implodeTokens() . '\\'; + } else { + $this->tokens[] = $token; + } + break; + + case T_CLASS: + case T_INTERFACE: + $this->tokens[] = $token; + if ( is_array( $token ) && $token[0] === T_STRING ) { + $this->classes[] = $this->namespace . $this->implodeTokens(); + } + } + } + + /** + * Returns the string representation of the tokens within the + * current expect sequence and resets the sequence. + * + * @return string + */ + protected function implodeTokens() { + $content = array(); + foreach ( $this->tokens as $token ) { + $content[] = is_string( $token ) ? $token : $token[1]; + } + + $this->tokens = array(); + $this->startToken = null; + + return trim( implode( '', $content ), " \n\t" ); + } +} diff --git a/includes/utils/Cdb.php b/includes/utils/Cdb.php deleted file mode 100644 index 3ceb620f..00000000 --- a/includes/utils/Cdb.php +++ /dev/null @@ -1,163 +0,0 @@ -<?php -/** - * Native CDB file reader and writer. - * - * 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 - */ - -/** - * Read from a CDB file. - * Native and pure PHP implementations are provided. - * http://cr.yp.to/cdb.html - */ -abstract class CdbReader { - /** - * The file handle - */ - protected $handle; - - /** - * Open a file and return a subclass instance - * - * @param string $fileName - * - * @return CdbReader - */ - public static function open( $fileName ) { - return self::haveExtension() ? - new CdbReaderDBA( $fileName ) : - new CdbReaderPHP( $fileName ); - } - - /** - * Returns true if the native extension is available - * - * @return bool - */ - public static function haveExtension() { - if ( !function_exists( 'dba_handlers' ) ) { - return false; - } - $handlers = dba_handlers(); - if ( !in_array( 'cdb', $handlers ) || !in_array( 'cdb_make', $handlers ) ) { - return false; - } - - return true; - } - - /** - * Create the object and open the file - * - * @param string $fileName - */ - abstract public function __construct( $fileName ); - - /** - * Close the file. Optional, you can just let the variable go out of scope. - */ - abstract public function close(); - - /** - * Get a value with a given key. Only string values are supported. - * - * @param string $key - */ - abstract public function get( $key ); -} - -/** - * Write to a CDB file. - * Native and pure PHP implementations are provided. - */ -abstract class CdbWriter { - /** - * The file handle - */ - protected $handle; - - /** - * File we'll be writing to when we're done - * @var string - */ - protected $realFileName; - - /** - * File we write to temporarily until we're done - * @var string - */ - protected $tmpFileName; - - /** - * Open a writer and return a subclass instance. - * The user must have write access to the directory, for temporary file creation. - * - * @param string $fileName - * - * @return CdbWriterDBA|CdbWriterPHP - */ - public static function open( $fileName ) { - return CdbReader::haveExtension() ? - new CdbWriterDBA( $fileName ) : - new CdbWriterPHP( $fileName ); - } - - /** - * Create the object and open the file - * - * @param string $fileName - */ - abstract public function __construct( $fileName ); - - /** - * Set a key to a given value. The value will be converted to string. - * @param string $key - * @param string $value - */ - abstract public function set( $key, $value ); - - /** - * Close the writer object. You should call this function before the object - * goes out of scope, to write out the final hashtables. - */ - abstract public function close(); - - /** - * If the object goes out of scope, close it for sanity - */ - public function __destruct() { - if ( isset( $this->handle ) ) { - $this->close(); - } - } - - /** - * Are we running on Windows? - * @return bool - */ - protected function isWindows() { - return substr( php_uname(), 0, 7 ) == 'Windows'; - } -} - -/** - * Exception for Cdb errors. - * This explicitly doesn't subclass MWException to encourage reuse. - */ -class CdbException extends Exception { -} diff --git a/includes/utils/CdbDBA.php b/includes/utils/CdbDBA.php deleted file mode 100644 index efcaf21f..00000000 --- a/includes/utils/CdbDBA.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php -/** - * DBA-based CDB reader/writer - * - * 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 - */ - -/** - * Reader class which uses the DBA extension - */ -class CdbReaderDBA extends CdbReader { - public function __construct( $fileName ) { - $this->handle = dba_open( $fileName, 'r-', 'cdb' ); - if ( !$this->handle ) { - throw new CdbException( 'Unable to open CDB file "' . $fileName . '"' ); - } - } - - public function close() { - if ( isset( $this->handle ) ) { - dba_close( $this->handle ); - } - unset( $this->handle ); - } - - public function get( $key ) { - return dba_fetch( $key, $this->handle ); - } -} - -/** - * Writer class which uses the DBA extension - */ -class CdbWriterDBA extends CdbWriter { - public function __construct( $fileName ) { - $this->realFileName = $fileName; - $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff ); - $this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' ); - if ( !$this->handle ) { - throw new CdbException( 'Unable to open CDB file for write "' . $fileName . '"' ); - } - } - - public function set( $key, $value ) { - return dba_insert( $key, $value, $this->handle ); - } - - public function close() { - if ( isset( $this->handle ) ) { - dba_close( $this->handle ); - } - if ( $this->isWindows() ) { - unlink( $this->realFileName ); - } - if ( !rename( $this->tmpFileName, $this->realFileName ) ) { - throw new CdbException( 'Unable to move the new CDB file into place.' ); - } - unset( $this->handle ); - } -} diff --git a/includes/utils/CdbPHP.php b/includes/utils/CdbPHP.php deleted file mode 100644 index 19d747a7..00000000 --- a/includes/utils/CdbPHP.php +++ /dev/null @@ -1,494 +0,0 @@ -<?php -/** - * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that - * appears in PHP 5.3. Changes are: - * * Error returns replaced with exceptions - * * Exception thrown if sizes or offsets are between 2GB and 4GB - * * Some variables renamed - * - * 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 - */ - -/** - * Common functions for readers and writers - */ -class CdbFunctions { - /** - * Take a modulo of a signed integer as if it were an unsigned integer. - * $b must be less than 0x40000000 and greater than 0 - * - * @param int $a - * @param int $b - * - * @return int - */ - public static function unsignedMod( $a, $b ) { - if ( $a & 0x80000000 ) { - $m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b ); - - return $m % $b; - } else { - return $a % $b; - } - } - - /** - * Shift a signed integer right as if it were unsigned - * @param int $a - * @param int $b - * @return int - */ - public static function unsignedShiftRight( $a, $b ) { - if ( $b == 0 ) { - return $a; - } - if ( $a & 0x80000000 ) { - return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) ); - } else { - return $a >> $b; - } - } - - /** - * The CDB hash function. - * - * @param string $s - * - * @return int - */ - public static function hash( $s ) { - $h = 5381; - $len = strlen( $s ); - for ( $i = 0; $i < $len; $i++ ) { - $h5 = ( $h << 5 ) & 0xffffffff; - // Do a 32-bit sum - // Inlined here for speed - $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff ); - $h = - ( - ( $sum & 0x40000000 ? 1 : 0 ) - + ( $h & 0x80000000 ? 2 : 0 ) - + ( $h & 0x40000000 ? 1 : 0 ) - + ( $h5 & 0x80000000 ? 2 : 0 ) - + ( $h5 & 0x40000000 ? 1 : 0 ) - ) << 30 - | ( $sum & 0x3fffffff ); - $h ^= ord( $s[$i] ); - $h &= 0xffffffff; - } - - return $h; - } -} - -/** - * CDB reader class - */ -class CdbReaderPHP extends CdbReader { - /** The filename */ - protected $fileName; - - /* number of hash slots searched under this key */ - protected $loop; - - /* initialized if loop is nonzero */ - protected $khash; - - /* initialized if loop is nonzero */ - protected $kpos; - - /* initialized if loop is nonzero */ - protected $hpos; - - /* initialized if loop is nonzero */ - protected $hslots; - - /* initialized if findNext() returns true */ - protected $dpos; - - /* initialized if cdb_findnext() returns 1 */ - protected $dlen; - - /** - * @param string $fileName - * @throws CdbException - */ - public function __construct( $fileName ) { - $this->fileName = $fileName; - $this->handle = fopen( $fileName, 'rb' ); - if ( !$this->handle ) { - throw new CdbException( 'Unable to open CDB file "' . $this->fileName . '".' ); - } - $this->findStart(); - } - - public function close() { - if ( isset( $this->handle ) ) { - fclose( $this->handle ); - } - unset( $this->handle ); - } - - /** - * @param mixed $key - * @return bool|string - */ - public function get( $key ) { - // strval is required - if ( $this->find( strval( $key ) ) ) { - return $this->read( $this->dlen, $this->dpos ); - } else { - return false; - } - } - - /** - * @param string $key - * @param int $pos - * @return bool - */ - protected function match( $key, $pos ) { - $buf = $this->read( strlen( $key ), $pos ); - - return $buf === $key; - } - - protected function findStart() { - $this->loop = 0; - } - - /** - * @throws CdbException - * @param int $length - * @param int $pos - * @return string - */ - protected function read( $length, $pos ) { - if ( fseek( $this->handle, $pos ) == -1 ) { - // This can easily happen if the internal pointers are incorrect - throw new CdbException( - 'Seek failed, file "' . $this->fileName . '" may be corrupted.' ); - } - - if ( $length == 0 ) { - return ''; - } - - $buf = fread( $this->handle, $length ); - if ( $buf === false || strlen( $buf ) !== $length ) { - throw new CdbException( - 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' ); - } - - return $buf; - } - - /** - * Unpack an unsigned integer and throw an exception if it needs more than 31 bits - * @param string $s - * @throws CdbException - * @return mixed - */ - protected function unpack31( $s ) { - $data = unpack( 'V', $s ); - if ( $data[1] > 0x7fffffff ) { - throw new CdbException( - 'Error in CDB file "' . $this->fileName . '", integer too big.' ); - } - - return $data[1]; - } - - /** - * Unpack a 32-bit signed integer - * @param string $s - * @return int - */ - protected function unpackSigned( $s ) { - $data = unpack( 'va/vb', $s ); - - return $data['a'] | ( $data['b'] << 16 ); - } - - /** - * @param string $key - * @return bool - */ - protected function findNext( $key ) { - if ( !$this->loop ) { - $u = CdbFunctions::hash( $key ); - $buf = $this->read( 8, ( $u << 3 ) & 2047 ); - $this->hslots = $this->unpack31( substr( $buf, 4 ) ); - if ( !$this->hslots ) { - return false; - } - $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) ); - $this->khash = $u; - $u = CdbFunctions::unsignedShiftRight( $u, 8 ); - $u = CdbFunctions::unsignedMod( $u, $this->hslots ); - $u <<= 3; - $this->kpos = $this->hpos + $u; - } - - while ( $this->loop < $this->hslots ) { - $buf = $this->read( 8, $this->kpos ); - $pos = $this->unpack31( substr( $buf, 4 ) ); - if ( !$pos ) { - return false; - } - $this->loop += 1; - $this->kpos += 8; - if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) { - $this->kpos = $this->hpos; - } - $u = $this->unpackSigned( substr( $buf, 0, 4 ) ); - if ( $u === $this->khash ) { - $buf = $this->read( 8, $pos ); - $keyLen = $this->unpack31( substr( $buf, 0, 4 ) ); - if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) { - // Found - $this->dlen = $this->unpack31( substr( $buf, 4 ) ); - $this->dpos = $pos + 8 + $keyLen; - - return true; - } - } - } - - return false; - } - - /** - * @param mixed $key - * @return bool - */ - protected function find( $key ) { - $this->findStart(); - - return $this->findNext( $key ); - } -} - -/** - * CDB writer class - */ -class CdbWriterPHP extends CdbWriter { - protected $hplist; - - protected $numentries; - - protected $pos; - - /** - * @param string $fileName - */ - public function __construct( $fileName ) { - $this->realFileName = $fileName; - $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff ); - $this->handle = fopen( $this->tmpFileName, 'wb' ); - if ( !$this->handle ) { - $this->throwException( - 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' ); - } - $this->hplist = array(); - $this->numentries = 0; - $this->pos = 2048; // leaving space for the pointer array, 256 * 8 - if ( fseek( $this->handle, $this->pos ) == -1 ) { - $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' ); - } - } - - /** - * @param string $key - * @param string $value - */ - public function set( $key, $value ) { - if ( strval( $key ) === '' ) { - // DBA cross-check hack - return; - } - $this->addbegin( strlen( $key ), strlen( $value ) ); - $this->write( $key ); - $this->write( $value ); - $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) ); - } - - /** - * @throws CdbException - */ - public function close() { - $this->finish(); - if ( isset( $this->handle ) ) { - fclose( $this->handle ); - } - if ( $this->isWindows() && file_exists( $this->realFileName ) ) { - unlink( $this->realFileName ); - } - if ( !rename( $this->tmpFileName, $this->realFileName ) ) { - $this->throwException( 'Unable to move the new CDB file into place.' ); - } - unset( $this->handle ); - } - - /** - * @throws CdbException - * @param string $buf - */ - protected function write( $buf ) { - $len = fwrite( $this->handle, $buf ); - if ( $len !== strlen( $buf ) ) { - $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' ); - } - } - - /** - * @throws CdbException - * @param int $len - */ - protected function posplus( $len ) { - $newpos = $this->pos + $len; - if ( $newpos > 0x7fffffff ) { - $this->throwException( - 'A value in the CDB file "' . $this->tmpFileName . '" is too large.' ); - } - $this->pos = $newpos; - } - - /** - * @param int $keylen - * @param int $datalen - * @param int $h - */ - protected function addend( $keylen, $datalen, $h ) { - $this->hplist[] = array( - 'h' => $h, - 'p' => $this->pos - ); - - $this->numentries++; - $this->posplus( 8 ); - $this->posplus( $keylen ); - $this->posplus( $datalen ); - } - - /** - * @throws CdbException - * @param int $keylen - * @param int $datalen - */ - protected function addbegin( $keylen, $datalen ) { - if ( $keylen > 0x7fffffff ) { - $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' ); - } - if ( $datalen > 0x7fffffff ) { - $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' ); - } - $buf = pack( 'VV', $keylen, $datalen ); - $this->write( $buf ); - } - - /** - * @throws CdbException - */ - protected function finish() { - // Hack for DBA cross-check - $this->hplist = array_reverse( $this->hplist ); - - // Calculate the number of items that will be in each hashtable - $counts = array_fill( 0, 256, 0 ); - foreach ( $this->hplist as $item ) { - ++$counts[255 & $item['h']]; - } - - // Fill in $starts with the *end* indexes - $starts = array(); - $pos = 0; - for ( $i = 0; $i < 256; ++$i ) { - $pos += $counts[$i]; - $starts[$i] = $pos; - } - - // Excessively clever and indulgent code to simultaneously fill $packedTables - // with the packed hashtables, and adjust the elements of $starts - // to actually point to the starts instead of the ends. - $packedTables = array_fill( 0, $this->numentries, false ); - foreach ( $this->hplist as $item ) { - $packedTables[--$starts[255 & $item['h']]] = $item; - } - - $final = ''; - for ( $i = 0; $i < 256; ++$i ) { - $count = $counts[$i]; - - // The size of the hashtable will be double the item count. - // The rest of the slots will be empty. - $len = $count + $count; - $final .= pack( 'VV', $this->pos, $len ); - - $hashtable = array(); - for ( $u = 0; $u < $len; ++$u ) { - $hashtable[$u] = array( 'h' => 0, 'p' => 0 ); - } - - // Fill the hashtable, using the next empty slot if the hashed slot - // is taken. - for ( $u = 0; $u < $count; ++$u ) { - $hp = $packedTables[$starts[$i] + $u]; - $where = CdbFunctions::unsignedMod( - CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len ); - while ( $hashtable[$where]['p'] ) { - if ( ++$where == $len ) { - $where = 0; - } - } - $hashtable[$where] = $hp; - } - - // Write the hashtable - for ( $u = 0; $u < $len; ++$u ) { - $buf = pack( 'vvV', - $hashtable[$u]['h'] & 0xffff, - CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ), - $hashtable[$u]['p'] ); - $this->write( $buf ); - $this->posplus( 8 ); - } - } - - // Write the pointer array at the start of the file - rewind( $this->handle ); - if ( ftell( $this->handle ) != 0 ) { - $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' ); - } - $this->write( $final ); - } - - /** - * Clean up the temp file and throw an exception - * - * @param string $msg - * @throws CdbException - */ - protected function throwException( $msg ) { - if ( $this->handle ) { - fclose( $this->handle ); - unlink( $this->tmpFileName ); - } - throw new CdbException( $msg ); - } -} diff --git a/includes/utils/IP.php b/includes/utils/IP.php index 0e2db8cc..4441236d 100644 --- a/includes/utils/IP.php +++ b/includes/utils/IP.php @@ -628,6 +628,25 @@ class IP { strcmp( $hexIP, $end ) <= 0 ); } + /** + * Determines if an IP address is a list of CIDR a.b.c.d/n ranges. + * + * @since 1.25 + * + * @param string $ip the IP to check + * @param array $ranges the IP ranges, each element a range + * + * @return bool true if the specified adress belongs to the specified range; otherwise, false. + */ + public static function isInRanges( $ip, $ranges ) { + foreach ( $ranges as $range ) { + if ( self::isInRange( $ip, $range ) ) { + return true; + } + } + return false; + } + /** * Convert some unusual representations of IPv4 addresses to their * canonical dotted quad representation. @@ -698,7 +717,7 @@ class IP { */ public static function isTrustedProxy( $ip ) { $trusted = self::isConfiguredProxy( $ip ); - wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) ); + Hooks::run( 'IsTrustedProxy', array( &$ip, &$trusted ) ); return $trusted; } @@ -712,7 +731,6 @@ class IP { public static function isConfiguredProxy( $ip ) { global $wgSquidServers, $wgSquidServersNoPurge; - wfProfileIn( __METHOD__ ); // Quick check of known singular proxy servers $trusted = in_array( $ip, $wgSquidServers ); @@ -723,7 +741,6 @@ class IP { } $trusted = self::$proxyIpSet->match( $ip ); } - wfProfileOut( __METHOD__ ); return $trusted; } diff --git a/includes/utils/MWCryptHKDF.php b/includes/utils/MWCryptHKDF.php index cc136793..950dd846 100644 --- a/includes/utils/MWCryptHKDF.php +++ b/includes/utils/MWCryptHKDF.php @@ -103,6 +103,7 @@ class MWCryptHKDF { * @param string $algorithm Name of hashing algorithm * @param BagOStuff $cache * @param string|array $context Context to mix into HKDF context + * @throws MWException */ public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) { if ( strlen( $secretKeyMaterial ) < 16 ) { @@ -157,6 +158,7 @@ class MWCryptHKDF { /** * Return a singleton instance, based on the global configs. * @return HKDF + * @throws MWException */ protected static function singleton() { global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey; @@ -271,14 +273,15 @@ class MWCryptHKDF { * * @param string $hash Hashing Algorithm * @param string $prk A pseudorandom key of at least HashLen octets - * (usually, the output from the extract step) + * (usually, the output from the extract step) * @param string $info Optional context and application specific information - * (can be a zero-length string) + * (can be a zero-length string) * @param int $bytes Length of output keying material in bytes - * (<= 255*HashLen) + * (<= 255*HashLen) * @param string &$lastK Set by this function to the last block of the expansion. - * In MediaWiki, this is used to seed future Extractions. + * In MediaWiki, this is used to seed future Extractions. * @return string Cryptographically secure random string $bytes long + * @throws MWException */ private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) { $hashLen = MWCryptHKDF::$hashLength[$hash]; diff --git a/includes/utils/MWCryptRand.php b/includes/utils/MWCryptRand.php index b602f78e..e6c0e784 100644 --- a/includes/utils/MWCryptRand.php +++ b/includes/utils/MWCryptRand.php @@ -294,7 +294,6 @@ class MWCryptRand { * @see self::generate() */ public function realGenerate( $bytes, $forceStrong = false ) { - wfProfileIn( __METHOD__ ); wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers( 5 ) . "\n" ); @@ -314,7 +313,6 @@ class MWCryptRand { // entropy so this is also preferable to just trying to read urandom because it may work // on Windows systems as well. if ( function_exists( 'mcrypt_create_iv' ) ) { - wfProfileIn( __METHOD__ . '-mcrypt' ); $rem = $bytes - strlen( $buffer ); $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM ); if ( $iv === false ) { @@ -324,7 +322,6 @@ class MWCryptRand { wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" ); } - wfProfileOut( __METHOD__ . '-mcrypt' ); } } @@ -337,7 +334,6 @@ class MWCryptRand { if ( function_exists( 'openssl_random_pseudo_bytes' ) && ( !wfIsWindows() || version_compare( PHP_VERSION, '5.3.4', '>=' ) ) ) { - wfProfileIn( __METHOD__ . '-openssl' ); $rem = $bytes - strlen( $buffer ); $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong ); if ( $openssl_bytes === false ) { @@ -353,7 +349,6 @@ class MWCryptRand { // using it use it's say on whether the randomness is strong $this->strong = !!$openssl_strong; } - wfProfileOut( __METHOD__ . '-openssl' ); } } @@ -361,7 +356,6 @@ class MWCryptRand { if ( strlen( $buffer ) < $bytes && ( function_exists( 'stream_set_read_buffer' ) || $forceStrong ) ) { - wfProfileIn( __METHOD__ . '-fopen-urandom' ); $rem = $bytes - strlen( $buffer ); if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) { wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom " . @@ -400,7 +394,6 @@ class MWCryptRand { } else { wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" ); } - wfProfileOut( __METHOD__ . '-fopen-urandom' ); } // If we cannot use or generate enough data from a secure source @@ -414,12 +407,10 @@ class MWCryptRand { ": Falling back to using a pseudo random state to generate randomness.\n" ); } while ( strlen( $buffer ) < $bytes ) { - wfProfileIn( __METHOD__ . '-fallback' ); $buffer .= $this->hmac( $this->randomState(), mt_rand() ); // This code is never really cryptographically strong, if we use it // at all, then set strong to false. $this->strong = false; - wfProfileOut( __METHOD__ . '-fallback' ); } // Once the buffer has been filled up with enough random data to fulfill @@ -431,8 +422,6 @@ class MWCryptRand { wfDebug( __METHOD__ . ": " . strlen( $buffer ) . " bytes of randomness leftover in the buffer.\n" ); - wfProfileOut( __METHOD__ ); - return $generated; } diff --git a/includes/utils/MWFunction.php b/includes/utils/MWFunction.php index 3a0492dc..fa7eebe8 100644 --- a/includes/utils/MWFunction.php +++ b/includes/utils/MWFunction.php @@ -22,42 +22,19 @@ class MWFunction { - /** - * @deprecated since 1.22; use call_user_func() - * @param callable $callback - * @return mixed - */ - public static function call( $callback ) { - wfDeprecated( __METHOD__, '1.22' ); - $args = func_get_args(); - - return call_user_func_array( 'call_user_func', $args ); - } - - /** - * @deprecated since 1.22; use call_user_func_array() - * @param callable $callback - * @param array $argsarams - * @return mixed - */ - public static function callArray( $callback, $argsarams ) { - wfDeprecated( __METHOD__, '1.22' ); - - return call_user_func_array( $callback, $argsarams ); - } - /** * @param string $class * @param array $args * @return object + * @deprecated 1.25 Use ObjectFactory::getObjectFromSpec() instead */ public static function newObj( $class, $args = array() ) { - if ( !count( $args ) ) { - return new $class; - } - - $ref = new ReflectionClass( $class ); + wfDeprecated( __METHOD__, '1.25' ); - return $ref->newInstanceArgs( $args ); + return ObjectFactory::getObjectFromSpec( array( + 'class' => $class, + 'args' => $args, + 'closure_expansion' => false, + ) ); } } diff --git a/includes/utils/StringUtils.php b/includes/utils/StringUtils.php deleted file mode 100644 index 86f45122..00000000 --- a/includes/utils/StringUtils.php +++ /dev/null @@ -1,612 +0,0 @@ -<?php -/** - * Methods to play with strings. - * - * 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 - */ - -/** - * A collection of static methods to play with strings. - */ -class StringUtils { - /** - * Test whether a string is valid UTF-8. - * - * The function check for invalid byte sequences, overlong encoding but - * not for different normalisations. - * - * This relies internally on the mbstring function mb_check_encoding() - * hardcoded to check against UTF-8. Whenever the function is not available - * we fallback to a pure PHP implementation. Setting $disableMbstring to - * true will skip the use of mb_check_encoding, this is mostly intended for - * unit testing our internal implementation. - * - * @since 1.21 - * @note In MediaWiki 1.21, this function did not provide proper UTF-8 validation. - * In particular, the pure PHP code path did not in fact check for overlong forms. - * Beware of this when backporting code to that version of MediaWiki. - * - * @param string $value String to check - * @param bool $disableMbstring Whether to use the pure PHP - * implementation instead of trying mb_check_encoding. Intended for unit - * testing. Default: false - * - * @return bool Whether the given $value is a valid UTF-8 encoded string - */ - static function isUtf8( $value, $disableMbstring = false ) { - $value = (string)$value; - - // If the mbstring extension is loaded, use it. However, before PHP 5.4, values above - // U+10FFFF are incorrectly allowed, so we have to check for them separately. - if ( !$disableMbstring && function_exists( 'mb_check_encoding' ) ) { - static $newPHP; - if ( $newPHP === null ) { - $newPHP = !mb_check_encoding( "\xf4\x90\x80\x80", 'UTF-8' ); - } - - return mb_check_encoding( $value, 'UTF-8' ) && - ( $newPHP || preg_match( "/\xf4[\x90-\xbf]|[\xf5-\xff]/S", $value ) === 0 ); - } - - if ( preg_match( "/[\x80-\xff]/S", $value ) === 0 ) { - // String contains only ASCII characters, has to be valid - return true; - } - - // PCRE implements repetition using recursion; to avoid a stack overflow (and segfault) - // for large input, we check for invalid sequences (<= 5 bytes) rather than valid - // sequences, which can be as long as the input string is. Multiple short regexes are - // used rather than a single long regex for performance. - static $regexes; - if ( $regexes === null ) { - $cont = "[\x80-\xbf]"; - $after = "(?!$cont)"; // "(?:[^\x80-\xbf]|$)" would work here - $regexes = array( - // Continuation byte at the start - "/^$cont/", - - // ASCII byte followed by a continuation byte - "/[\\x00-\x7f]$cont/S", - - // Illegal byte - "/[\xc0\xc1\xf5-\xff]/S", - - // Invalid 2-byte sequence, or valid one then an extra continuation byte - "/[\xc2-\xdf](?!$cont$after)/S", - - // Invalid 3-byte sequence, or valid one then an extra continuation byte - "/\xe0(?![\xa0-\xbf]$cont$after)/", - "/[\xe1-\xec\xee\xef](?!$cont{2}$after)/S", - "/\xed(?![\x80-\x9f]$cont$after)/", - - // Invalid 4-byte sequence, or valid one then an extra continuation byte - "/\xf0(?![\x90-\xbf]$cont{2}$after)/", - "/[\xf1-\xf3](?!$cont{3}$after)/S", - "/\xf4(?![\x80-\x8f]$cont{2}$after)/", - ); - } - - foreach ( $regexes as $regex ) { - if ( preg_match( $regex, $value ) !== 0 ) { - return false; - } - } - - return true; - } - - /** - * Perform an operation equivalent to - * - * preg_replace( "!$startDelim(.*?)$endDelim!", $replace, $subject ); - * - * except that it's worst-case O(N) instead of O(N^2) - * - * Compared to delimiterReplace(), this implementation is fast but memory- - * hungry and inflexible. The memory requirements are such that I don't - * recommend using it on anything but guaranteed small chunks of text. - * - * @param string $startDelim - * @param string $endDelim - * @param string $replace - * @param string $subject - * - * @return string - */ - static function hungryDelimiterReplace( $startDelim, $endDelim, $replace, $subject ) { - $segments = explode( $startDelim, $subject ); - $output = array_shift( $segments ); - foreach ( $segments as $s ) { - $endDelimPos = strpos( $s, $endDelim ); - if ( $endDelimPos === false ) { - $output .= $startDelim . $s; - } else { - $output .= $replace . substr( $s, $endDelimPos + strlen( $endDelim ) ); - } - } - - return $output; - } - - /** - * Perform an operation equivalent to - * - * preg_replace_callback( "!$startDelim(.*)$endDelim!s$flags", $callback, $subject ) - * - * This implementation is slower than hungryDelimiterReplace but uses far less - * memory. The delimiters are literal strings, not regular expressions. - * - * If the start delimiter ends with an initial substring of the end delimiter, - * e.g. in the case of C-style comments, the behavior differs from the model - * regex. In this implementation, the end must share no characters with the - * start, so e.g. /*\/ is not considered to be both the start and end of a - * comment. /*\/xy/*\/ is considered to be a single comment with contents /xy/. - * - * @param string $startDelim Start delimiter - * @param string $endDelim End delimiter - * @param callable $callback Function to call on each match - * @param string $subject - * @param string $flags Regular expression flags - * @throws MWException - * @return string - */ - static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, - $subject, $flags = '' - ) { - $inputPos = 0; - $outputPos = 0; - $output = ''; - $foundStart = false; - $encStart = preg_quote( $startDelim, '!' ); - $encEnd = preg_quote( $endDelim, '!' ); - $strcmp = strpos( $flags, 'i' ) === false ? 'strcmp' : 'strcasecmp'; - $endLength = strlen( $endDelim ); - $m = array(); - - while ( $inputPos < strlen( $subject ) && - preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) - ) { - $tokenOffset = $m[0][1]; - if ( $m[1][0] != '' ) { - if ( $foundStart && - $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 - ) { - # An end match is present at the same location - $tokenType = 'end'; - $tokenLength = $endLength; - } else { - $tokenType = 'start'; - $tokenLength = strlen( $m[0][0] ); - } - } elseif ( $m[2][0] != '' ) { - $tokenType = 'end'; - $tokenLength = strlen( $m[0][0] ); - } else { - throw new MWException( 'Invalid delimiter given to ' . __METHOD__ ); - } - - if ( $tokenType == 'start' ) { - # Only move the start position if we haven't already found a start - # This means that START START END matches outer pair - if ( !$foundStart ) { - # Found start - $inputPos = $tokenOffset + $tokenLength; - # Write out the non-matching section - $output .= substr( $subject, $outputPos, $tokenOffset - $outputPos ); - $outputPos = $tokenOffset; - $contentPos = $inputPos; - $foundStart = true; - } else { - # Move the input position past the *first character* of START, - # to protect against missing END when it overlaps with START - $inputPos = $tokenOffset + 1; - } - } elseif ( $tokenType == 'end' ) { - if ( $foundStart ) { - # Found match - $output .= call_user_func( $callback, array( - substr( $subject, $outputPos, $tokenOffset + $tokenLength - $outputPos ), - substr( $subject, $contentPos, $tokenOffset - $contentPos ) - ) ); - $foundStart = false; - } else { - # Non-matching end, write it out - $output .= substr( $subject, $inputPos, $tokenOffset + $tokenLength - $outputPos ); - } - $inputPos = $outputPos = $tokenOffset + $tokenLength; - } else { - throw new MWException( 'Invalid delimiter given to ' . __METHOD__ ); - } - } - if ( $outputPos < strlen( $subject ) ) { - $output .= substr( $subject, $outputPos ); - } - - return $output; - } - - /** - * Perform an operation equivalent to - * - * preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject ) - * - * @param string $startDelim Start delimiter regular expression - * @param string $endDelim End delimiter regular expression - * @param string $replace Replacement string. May contain $1, which will be - * replaced by the text between the delimiters - * @param string $subject String to search - * @param string $flags Regular expression flags - * @return string The string with the matches replaced - */ - static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) { - $replacer = new RegexlikeReplacer( $replace ); - - return self::delimiterReplaceCallback( $startDelim, $endDelim, - $replacer->cb(), $subject, $flags ); - } - - /** - * More or less "markup-safe" explode() - * Ignores any instances of the separator inside <...> - * @param string $separator - * @param string $text - * @return array - */ - static function explodeMarkup( $separator, $text ) { - $placeholder = "\x00"; - - // Remove placeholder instances - $text = str_replace( $placeholder, '', $text ); - - // Replace instances of the separator inside HTML-like tags with the placeholder - $replacer = new DoubleReplacer( $separator, $placeholder ); - $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text ); - - // Explode, then put the replaced separators back in - $items = explode( $separator, $cleaned ); - foreach ( $items as $i => $str ) { - $items[$i] = str_replace( $placeholder, $separator, $str ); - } - - return $items; - } - - /** - * Escape a string to make it suitable for inclusion in a preg_replace() - * replacement parameter. - * - * @param string $string - * @return string - */ - static function escapeRegexReplacement( $string ) { - $string = str_replace( '\\', '\\\\', $string ); - $string = str_replace( '$', '\\$', $string ); - - return $string; - } - - /** - * Workalike for explode() with limited memory usage. - * Returns an Iterator - * @param string $separator - * @param string $subject - * @return ArrayIterator|ExplodeIterator - */ - static function explode( $separator, $subject ) { - if ( substr_count( $subject, $separator ) > 1000 ) { - return new ExplodeIterator( $separator, $subject ); - } else { - return new ArrayIterator( explode( $separator, $subject ) ); - } - } -} - -/** - * Base class for "replacers", objects used in preg_replace_callback() and - * StringUtils::delimiterReplaceCallback() - */ -class Replacer { - /** - * @return array - */ - function cb() { - return array( &$this, 'replace' ); - } -} - -/** - * Class to replace regex matches with a string similar to that used in preg_replace() - */ -class RegexlikeReplacer extends Replacer { - private $r; - - /** - * @param string $r - */ - function __construct( $r ) { - $this->r = $r; - } - - /** - * @param array $matches - * @return string - */ - function replace( $matches ) { - $pairs = array(); - foreach ( $matches as $i => $match ) { - $pairs["\$$i"] = $match; - } - - return strtr( $this->r, $pairs ); - } -} - -/** - * Class to perform secondary replacement within each replacement string - */ -class DoubleReplacer extends Replacer { - /** - * @param mixed $from - * @param mixed $to - * @param int $index - */ - function __construct( $from, $to, $index = 0 ) { - $this->from = $from; - $this->to = $to; - $this->index = $index; - } - - /** - * @param array $matches - * @return mixed - */ - function replace( $matches ) { - return str_replace( $this->from, $this->to, $matches[$this->index] ); - } -} - -/** - * Class to perform replacement based on a simple hashtable lookup - */ -class HashtableReplacer extends Replacer { - private $table, $index; - - /** - * @param array $table - * @param int $index - */ - function __construct( $table, $index = 0 ) { - $this->table = $table; - $this->index = $index; - } - - /** - * @param array $matches - * @return mixed - */ - function replace( $matches ) { - return $this->table[$matches[$this->index]]; - } -} - -/** - * Replacement array for FSS with fallback to strtr() - * Supports lazy initialisation of FSS resource - */ -class ReplacementArray { - private $data = false; - private $fss = false; - - /** - * Create an object with the specified replacement array - * The array should have the same form as the replacement array for strtr() - * @param array $data - */ - function __construct( $data = array() ) { - $this->data = $data; - } - - /** - * @return array - */ - function __sleep() { - return array( 'data' ); - } - - function __wakeup() { - $this->fss = false; - } - - /** - * Set the whole replacement array at once - * @param array $data - */ - function setArray( $data ) { - $this->data = $data; - $this->fss = false; - } - - /** - * @return array|bool - */ - function getArray() { - return $this->data; - } - - /** - * Set an element of the replacement array - * @param string $from - * @param string $to - */ - function setPair( $from, $to ) { - $this->data[$from] = $to; - $this->fss = false; - } - - /** - * @param array $data - */ - function mergeArray( $data ) { - $this->data = array_merge( $this->data, $data ); - $this->fss = false; - } - - /** - * @param ReplacementArray $other - */ - function merge( $other ) { - $this->data = array_merge( $this->data, $other->data ); - $this->fss = false; - } - - /** - * @param string $from - */ - function removePair( $from ) { - unset( $this->data[$from] ); - $this->fss = false; - } - - /** - * @param array $data - */ - function removeArray( $data ) { - foreach ( $data as $from => $to ) { - $this->removePair( $from ); - } - $this->fss = false; - } - - /** - * @param string $subject - * @return string - */ - function replace( $subject ) { - if ( function_exists( 'fss_prep_replace' ) ) { - wfProfileIn( __METHOD__ . '-fss' ); - if ( $this->fss === false ) { - $this->fss = fss_prep_replace( $this->data ); - } - $result = fss_exec_replace( $this->fss, $subject ); - wfProfileOut( __METHOD__ . '-fss' ); - } else { - wfProfileIn( __METHOD__ . '-strtr' ); - $result = strtr( $subject, $this->data ); - wfProfileOut( __METHOD__ . '-strtr' ); - } - - return $result; - } -} - -/** - * An iterator which works exactly like: - * - * foreach ( explode( $delim, $s ) as $element ) { - * ... - * } - * - * Except it doesn't use 193 byte per element - */ -class ExplodeIterator implements Iterator { - // The subject string - private $subject, $subjectLength; - - // The delimiter - private $delim, $delimLength; - - // The position of the start of the line - private $curPos; - - // The position after the end of the next delimiter - private $endPos; - - // The current token - private $current; - - /** - * Construct a DelimIterator - * @param string $delim - * @param string $subject - */ - function __construct( $delim, $subject ) { - $this->subject = $subject; - $this->delim = $delim; - - // Micro-optimisation (theoretical) - $this->subjectLength = strlen( $subject ); - $this->delimLength = strlen( $delim ); - - $this->rewind(); - } - - function rewind() { - $this->curPos = 0; - $this->endPos = strpos( $this->subject, $this->delim ); - $this->refreshCurrent(); - } - - function refreshCurrent() { - if ( $this->curPos === false ) { - $this->current = false; - } elseif ( $this->curPos >= $this->subjectLength ) { - $this->current = ''; - } elseif ( $this->endPos === false ) { - $this->current = substr( $this->subject, $this->curPos ); - } else { - $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos ); - } - } - - function current() { - return $this->current; - } - - /** - * @return int|bool Current position or boolean false if invalid - */ - function key() { - return $this->curPos; - } - - /** - * @return string - */ - function next() { - if ( $this->endPos === false ) { - $this->curPos = false; - } else { - $this->curPos = $this->endPos + $this->delimLength; - if ( $this->curPos >= $this->subjectLength ) { - $this->endPos = false; - } else { - $this->endPos = strpos( $this->subject, $this->delim, $this->curPos ); - } - } - $this->refreshCurrent(); - - return $this->current; - } - - /** - * @return bool - */ - function valid() { - return $this->curPos !== false; - } -} diff --git a/includes/utils/UIDGenerator.php b/includes/utils/UIDGenerator.php index 5346afa6..92415877 100644 --- a/includes/utils/UIDGenerator.php +++ b/includes/utils/UIDGenerator.php @@ -119,6 +119,7 @@ class UIDGenerator { /** * @param array $info (UIDGenerator::millitime(), counter, clock sequence) * @return string 88 bits + * @throws MWException */ protected function getTimestampedID88( array $info ) { list( $time, $counter ) = $info; @@ -163,6 +164,7 @@ class UIDGenerator { /** * @param array $info (UIDGenerator::millitime(), counter, clock sequence) * @return string 128 bits + * @throws MWException */ protected function getTimestampedID128( array $info ) { list( $time, $counter, $clkSeq ) = $info; @@ -260,6 +262,7 @@ class UIDGenerator { * @param int $count Number of IDs to return (1 to 10000) * @param int $flags (supports UIDGenerator::QUICK_VOLATILE) * @return array Ordered list of float integer values + * @throws MWException */ protected function getSequentialPerNodeIDs( $bucket, $bits, $count, $flags ) { if ( $count <= 0 ) { @@ -278,7 +281,7 @@ class UIDGenerator { if ( ( $flags & self::QUICK_VOLATILE ) && PHP_SAPI !== 'cli' ) { try { $cache = ObjectCache::newAccelerator( array() ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { // not supported } } @@ -436,6 +439,7 @@ class UIDGenerator { /** * @param array $time Result of UIDGenerator::millitime() * @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201) + * @throws MWException */ protected function millisecondsSinceEpochBinary( array $time ) { list( $sec, $msec ) = $time; diff --git a/includes/utils/ZipDirectoryReader.php b/includes/utils/ZipDirectoryReader.php index bc849766..86960aa1 100644 --- a/includes/utils/ZipDirectoryReader.php +++ b/includes/utils/ZipDirectoryReader.php @@ -186,6 +186,7 @@ class ZipDirectoryReader { * Throw an error, and log a debug message * @param mixed $code * @param string $debugMessage + * @throws ZipDirectoryReaderError */ function error( $code, $debugMessage ) { wfDebug( __CLASS__ . ": Fatal error: $debugMessage\n" ); -- cgit v1.2.3-54-g00ecf