diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2015-06-04 07:31:04 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2015-06-04 07:58:39 +0200 |
commit | f6d65e533c62f6deb21342d4901ece24497b433e (patch) | |
tree | f28adf0362d14bcd448f7b65a7aaf38650f923aa /includes | |
parent | c27b2e832fe25651ef2410fae85b41072aae7519 (diff) |
Update to MediaWiki 1.25.1
Diffstat (limited to 'includes')
827 files changed, 52489 insertions, 33391 deletions
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 . '</div>'; - 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 = '<span class="redirect-in-category">' . $link . '</span>'; - } - $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 = '<span class="redirect-in-category">' . $link . '</span>'; + } + + 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 = '<span class="redirect-in-category">' . $link . '</span>'; - } - $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 = '<span class="redirect-in-category">' . $link . '</span>'; - } - $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 .= "<div id=\"mw-subcategories\">\n"; - $r .= '<h2>' . $this->msg( 'subcategories' )->text() . "</h2>\n"; + $r .= '<h2>' . $this->msg( 'subcategories' )->parse() . "</h2>\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 = "<div id=\"mw-pages\">\n"; - $r .= '<h2>' . $this->msg( 'category_header', $ti )->text() . "</h2>\n"; + $r .= '<h2>' . $this->msg( 'category_header', $ti )->parse() . "</h2>\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 = '<table style="width: 100%;"><tr style="vertical-align: top;">'; - $prevchar = null; + $ret = Html::openElement( 'div', array( 'class' => 'mw-category' ) ); - foreach ( $columns as $column ) { - $ret .= '<td style="width: 33.3%;">'; - $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 .= '<h3>' . $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 .= "</h3>\n"; + $ret .= '<div class="mw-category-group"><h3>' . $h3char; + $ret .= "</h3>\n"; - $ret .= '<ul><li>'; - $ret .= implode( "</li>\n<li>", $articles ); - $ret .= '</li></ul>'; - - $first = false; - $prevchar = $char; - } + $ret .= '<ul><li>'; + $ret .= implode( "</li>\n<li>", $articles ); + $ret .= '</li></ul></div>'; - $ret .= "</td>\n"; } - $ret .= '</tr></table>'; + $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 @@ +<?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 + */ + +/*** + * This file contains a set of backwards-compatability class names + * after the cdb functions were moved out into a separate library + * and put under a proper namespace + * + * @since 1.25 + */ + +/** + * @deprecated since 1.25 + */ +abstract class CdbReader extends \Cdb\Reader { +} + +/** + * @deprecated since 1.25 + */ +abstract class CdbWriter extends \Cdb\Writer { +} + +/** + * @deprecated since 1.25 + */ +class CdbException extends \Cdb\Exception { +} diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php index 94b7b7a9..09665dfb 100644 --- a/includes/ChangeTags.php +++ b/includes/ChangeTags.php @@ -18,10 +18,18 @@ * http://www.gnu.org/copyleft/gpl.html * * @file + * @ingroup Change tagging */ class ChangeTags { /** + * Can't delete tags with more than this many uses. Similar in intent to + * the bigdelete user right + * @todo Use the job queue for tag deletion to avoid this restriction + */ + const MAX_DELETE_USES = 5000; + + /** * Creates HTML for the given tags * * @param string $tags Comma-separated list of tags @@ -84,21 +92,50 @@ class ChangeTags { * * @throws MWException * @return bool False if no changes are made, otherwise true - * - * @exception MWException When $rc_id, $rev_id and $log_id are all null */ public static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) { - if ( !is_array( $tags ) ) { - $tags = array( $tags ); - } + $result = self::updateTags( $tags, null, $rc_id, $rev_id, $log_id, $params ); + return (bool)$result[0]; + } + + /** + * Add and remove tags to/from a change given its rc_id, rev_id and/or log_id, + * without verifying that the tags exist or are valid. If a tag is present in + * both $tagsToAdd and $tagsToRemove, it will be removed. + * + * This function should only be used by extensions to manipulate tags they + * have registered using the ListDefinedTags hook. When dealing with user + * input, call updateTagsWithChecks() instead. + * + * @param string|array|null $tagsToAdd Tags to add to the change + * @param string|array|null $tagsToRemove Tags to remove from the change + * @param int|null &$rc_id The rc_id of the change to add the tags to. + * Pass a variable whose value is null if the rc_id is not relevant or unknown. + * @param int|null &$rev_id The rev_id of the change to add the tags to. + * Pass a variable whose value is null if the rev_id is not relevant or unknown. + * @param int|null &$log_id The log_id of the change to add the tags to. + * Pass a variable whose value is null if the log_id is not relevant or unknown. + * @param string $params Params to put in the ct_params field of table + * 'change_tag' when adding tags + * + * @throws MWException When $rc_id, $rev_id and $log_id are all null + * @return array Index 0 is an array of tags actually added, index 1 is an + * array of tags actually removed, index 2 is an array of tags present on the + * revision or log entry before any changes were made + * + * @since 1.25 + */ + public static function updateTags( $tagsToAdd, $tagsToRemove, &$rc_id = null, + &$rev_id = null, &$log_id = null, $params = null ) { - $tags = array_filter( $tags ); // Make sure we're submitting all tags... + $tagsToAdd = array_filter( (array)$tagsToAdd ); // Make sure we're submitting all tags... + $tagsToRemove = array_filter( (array)$tagsToRemove ); if ( !$rc_id && !$rev_id && !$log_id ) { throw new MWException( 'At least one of: RCID, revision ID, and log ID MUST be ' . - 'specified when adding a tag to a change!' ); + 'specified when adding or removing a tag from a change!' ); } $dbw = wfGetDB( DB_MASTER ); @@ -137,11 +174,85 @@ class ChangeTags { ); } + // update the tag_summary row + $prevTags = array(); + if ( !self::updateTagSummaryRow( $tagsToAdd, $tagsToRemove, $rc_id, $rev_id, + $log_id, $prevTags ) ) { + + // nothing to do + return array( array(), array(), $prevTags ); + } + + // insert a row into change_tag for each new tag + if ( count( $tagsToAdd ) ) { + $tagsRows = array(); + foreach ( $tagsToAdd as $tag ) { + // Filter so we don't insert NULLs as zero accidentally. + // Keep in mind that $rc_id === null means "I don't care/know about the + // rc_id, just delete $tag on this revision/log entry". It doesn't + // mean "only delete tags on this revision/log WHERE rc_id IS NULL". + $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 + ) + ); + } + + $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,42 +260,277 @@ 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. * Needs $tables to be set up properly, so we can figure out which join conditions to use. @@ -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' ) ) ); @@ -290,18 +636,451 @@ class ChangeTags { } /** + * 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. @@ -326,12 +1130,45 @@ class ChangeTags { } /** + * 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/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'; @@ -221,11 +224,18 @@ $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, @@ -260,6 +270,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 */ @@ -273,6 +300,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' ), ); @@ -1245,6 +1283,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 "<gallery>" tag */ $wgGalleryOptions = array( @@ -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 @@ -1682,6 +1765,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 * Fields are: @@ -1960,15 +2049,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 */ $wgMaxArticleSize = 2048; @@ -2063,43 +2143,21 @@ $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: - * <code> - * BloomCache::get( 'main' )->init( 'shared', 1000000000, .001 ); - * </code> - * 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; * <cross-domain-policy>. 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(); @@ -3374,15 +3440,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( @@ -3715,6 +3775,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 * as 'redirected from' links. @@ -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; @@ -4109,15 +4174,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 * {{NUMBEROFACTIVEUSERS}} magic word in wikitext. @@ -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,15 +5290,41 @@ $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. * * Useful for developers and technical users trying to working on a closed wiki. @@ -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... @@ -5390,12 +5490,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 * templates. @@ -5430,25 +5524,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,10 +5569,25 @@ $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 * want reduce load caused by cached scripts still pulling suggestions. @@ -5512,6 +5602,11 @@ $wgEnableOpenSearchSuggest = true; $wgOpenSearchDefaultLimit = 10; /** + * Minimum length of extract in <Description>. Actual extracts will last until the end of sentence. + */ +$wgOpenSearchDescriptionLength = 100; + +/** * Expiry time for search suggestion responses */ $wgSearchSuggestCacheExpiry = 1200; @@ -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 @@ -5877,11 +5972,6 @@ $wgAdvertisedFeedTypes = array( 'atom' ); $wgRCShowWatchingUsers = false; # UPO /** - * Show watching users in Page views - */ -$wgPageShowWatchingUsers = false; - -/** * Show the amount of changed characters in recent changes */ $wgRCShowChangedSize = true; @@ -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, @@ -6967,6 +7067,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 * from the origin domain by the Access-Control-Allow-Origin header. @@ -7084,6 +7190,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'; /** @@ -7312,6 +7438,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 @@ -151,6 +151,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 */ const AS_PARSE_ERROR = 240; @@ -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( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' ); + # If this is a default message (but not css or js), + # show a hint that it is translatable on translatewiki.net + if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS ) + && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) + ) { + $defaultMessageText = $this->mTitle->getDefaultMessageText(); + if ( $defaultMessageText !== false ) { + $wgOut->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>", + '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( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>", 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 <noinclude>'s into account. - $wgOut->addWikiTextTitleTidy( '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', $this->mTitle ); + $wgOut->addWikiTextTitleTidy( + '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', + $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( + '<div class="mw-editnotice-notext">' + . $msg->parseAsBlock() + . '</div>' + ); + } + } if ( $this->isConflict ) { $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' ); @@ -2587,6 +2636,10 @@ class EditPage { $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' ); } + if ( $this->selfRedirect ) { + $wgOut->wrapWikiMsg( "<div id='mw-selfredirect'>\n$1\n</div>", 'selfredirect' ); + } + if ( $this->hookError !== '' ) { $wgOut->addWikiText( $this->hookError ); } @@ -2846,7 +2899,7 @@ class EditPage { global $wgOut; $section = htmlspecialchars( $this->section ); $wgOut->addHTML( <<<HTML -<input type='hidden' value="{$section}" name="wpSection" /> +<input type='hidden' value="{$section}" name="wpSection"/> <input type='hidden' value="{$this->starttime}" name="wpStarttime" /> <input type='hidden' value="{$this->edittime}" name="wpEdittime" /> <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" /> @@ -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( '</div>' ); @@ -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( '<div class="mw-tos-summary">' ); @@ -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 "<div id=\"editpage-copywarn\">\n" . call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>"; @@ -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( "<div class='editOptions'>\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( " <span class='editHelp'>{$edithelp}</span>\n" ); $wgOut->addHTML( "</div><!-- editButtons -->\n" ); - wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) ); + Hooks::run( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) ); $wgOut->addHTML( "</div><!-- editOptions -->\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( '<h2>$1</h2>', "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( "<div class='previewnote'>" . wfMessage( 'session_fail_preview_html' )->text() . "</div>", 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 = '<div id="toolbar"></div>'; - 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,83 +3821,39 @@ 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, "<s>maybe wrap them in XML - * or something...</s>" 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 = - '<?xml version="1.0" encoding="UTF-8" ?>' . "\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 = "<contributors>"; // rev_deleted @@ -239,7 +238,6 @@ class WikiExporter { "</contributor>"; } $this->author_list .= "</contributors>"; - 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__ ); } /** @@ -480,16 +473,6 @@ class WikiExporter { */ 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 "<mediawiki>" element. * This does not include an xml directive, so is safe to include * as a subelement in a larger XML stream. Namespace and XML Schema @@ -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 = " <revision>\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 .= " <sha1/>\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 .= " </revision>\n"; - wfProfileOut( __METHOD__ ); return $out; } @@ -749,7 +730,6 @@ class XmlDumpWriter { * @access private */ function writeLogItem( $row ) { - wfProfileIn( __METHOD__ ); $out = " <logitem>\n"; $out .= " " . Xml::element( 'id', null, strval( $row->log_id ) ) . "\n"; @@ -783,7 +763,6 @@ class XmlDumpWriter { $out .= " </logitem>\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 = '<p>' . 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 = "<p>Can't load revision $newid</p>"; } 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. */ @@ -161,6 +165,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 * @param array $b @@ -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 = "<ul>\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 .= '<li>' . $file . ' line ' . $line . ' calls '; - } - if ( !empty( $call['class'] ) ) { - $msg .= $call['class'] . $call['type']; - } - $msg .= $call['function'] . '()'; - if ( $wgCommandLineMode ) { - $msg .= "\n"; - } else { - $msg .= "</li>\n"; - } - } - if ( $wgCommandLineMode ) { - $msg .= "\n"; + if ( $raw ) { + $frameFormat = "%s line %s calls %s()\n"; + $traceFormat = "%s"; } else { - $msg .= "</ul>\n"; + $frameFormat = "<li>%s line %s calls %s()</li>\n"; + $traceFormat = "<ul>\n%s</ul>\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 "<!doctype html>" . @@ -2496,20 +2468,6 @@ function wfIsHHVM() { } /** - * 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 * environment variables are then checked in sequence, and if none are @@ -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() ) ); } /** @@ -3017,15 +2983,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 * should consist of two consecutive items in the array (do not use "--option value"). @@ -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 ) { @@ -3973,16 +3956,6 @@ function wfGetParserCacheStorage() { } /** - * 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 * * @param string $event Event name @@ -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,11 +4092,23 @@ 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 @@ -102,6 +102,35 @@ class Html { ); /** + * 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. * Removes size attribute when $wgUseMediaWikiUIEverywhere enabled. @@ -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 <html> and <head> tags can be omitted under - // pretty much any sane circumstances, if they have no attributes. See: - // <http://www.whatwg.org/html/syntax.html#optional-tags> - 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 <select> - $ret .= Html::openElement( 'select', $selectAttribs ) + $ret .= self::openElement( 'select', $selectAttribs ) . "\n" . implode( "\n", $optionsHtml ) . "\n" - . Html::closeElement( 'select' ); + . self::closeElement( 'select' ); return $ret; } @@ -911,7 +965,7 @@ class Html { $attribs['version'] = $wgHtml5Version; } - $html = Html::openElement( 'html', $attribs ); + $html = self::openElement( 'html', $attribs ); if ( $html ) { $html .= "\n"; @@ -946,44 +1000,58 @@ class Html { * * @return string */ - static function infoBox( $text, $icon, $alt, $class = false ) { - $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) ); + static function infoBox( $text, $icon, $alt, $class = '' ) { + $s = self::openElement( 'div', array( 'class' => "mw-infobox $class" ) ); - $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) . - Html::element( 'img', + $s .= self::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) . + self::element( 'img', array( 'src' => $icon, 'alt' => $alt, ) ) . - Html::closeElement( 'div' ); + self::closeElement( 'div' ); - $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ) . + $s .= self::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ) . $text . - Html::closeElement( 'div' ); - $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' ); + self::closeElement( 'div' ); + $s .= self::element( 'div', array( 'style' => 'clear: left;' ), ' ' ); - $s .= Html::closeElement( 'div' ); + $s .= self::closeElement( 'div' ); - $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' ); + $s .= self::element( 'div', array( 'style' => 'clear: left;' ), ' ' ); return $s; } /** - * Generate a srcset attribute value from an array mapping pixel densities - * to URLs. Note that srcset supports width and height values as well, which - * are not used here. + * Generate a srcset attribute value. + * + * Generates a srcset attribute value from an array mapping pixel densities + * to URLs. A trailing 'x' in pixel density values is optional. + * + * @note srcset width and height values are not supported. + * + * @see http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset + * + * @par Example: + * @code + * Html::srcSet( array( + * '1x' => 'standard.jpeg', + * '1.5x' => 'large.jpeg', + * '3x' => 'extra-large.jpeg', + * ) ); + * // gives 'standard.jpeg 1x, large.jpeg 1.5x, extra-large.jpeg 2x' + * @endcode * - * @param array $urls + * @param string[] $urls * @return string */ static function srcSet( $urls ) { $candidates = array(); foreach ( $urls as $density => $url ) { - // Image candidate syntax per current whatwg live spec, 2012-09-23: - // http://www.whatwg.org/html/embedded-content-1.html#attr-img-srcset - $candidates[] = "{$url} {$density}x"; + // Cast density to float to strip 'x'. + $candidates[] = $url . ' ' . (float)$density . 'x'; } return implode( ", ", $candidates ); } diff --git a/includes/HtmlFormatter.php b/includes/HtmlFormatter.php index ccbfba82..b2926d17 100644 --- a/includes/HtmlFormatter.php +++ b/includes/HtmlFormatter.php @@ -133,7 +133,6 @@ class HtmlFormatter { * @return array Array of removed DOMElements */ public function filterContent() { - wfProfileIn( __METHOD__ ); $removals = $this->parseItemsToRemove(); // Bail out early if nothing to do @@ -143,7 +142,6 @@ class HtmlFormatter { }, true ) ) { - wfProfileOut( __METHOD__ ); return array(); } @@ -178,7 +176,7 @@ class HtmlFormatter { // CSS Classes $domElemsToRemove = array(); - $xpath = new DOMXpath( $doc ); + $xpath = new DOMXPath( $doc ); foreach ( $removals['CLASS'] as $classToRemove ) { $elements = $xpath->query( '//*[contains(@class, "' . $classToRemove . '")]' ); @@ -202,7 +200,6 @@ class HtmlFormatter { $removed = array_merge( $removed, $this->removeElements( $elements ) ); } - wfProfileOut( __METHOD__ ); return $removed; } @@ -235,7 +232,6 @@ class HtmlFormatter { * @return string */ private function fixLibXML( $html ) { - wfProfileIn( __METHOD__ ); static $replacements; if ( !$replacements ) { // We don't include rules like '"' => '&quot;' because entities had already been @@ -249,7 +245,6 @@ class HtmlFormatter { } $html = $replacements->replace( $html ); $html = mb_convert_encoding( $html, 'UTF-8', 'HTML-ENTITIES' ); - wfProfileOut( __METHOD__ ); return $html; } @@ -264,10 +259,8 @@ class HtmlFormatter { * @return string Processed HTML */ public function getText( $element = null ) { - wfProfileIn( __METHOD__ ); if ( $this->doc ) { - wfProfileIn( __METHOD__ . '-dom' ); if ( $element !== null && !( $element instanceof DOMElement ) ) { $element = $this->doc->getElementById( $element ); } @@ -283,9 +276,7 @@ class HtmlFormatter { $body->appendChild( $element ); } $html = $this->doc->saveHTML(); - wfProfileOut( __METHOD__ . '-dom' ); - wfProfileIn( __METHOD__ . '-fixes' ); $html = $this->fixLibXml( $html ); if ( wfIsWindows() ) { // Cleanup for CRLF misprocessing of unknown origin on Windows. @@ -294,7 +285,6 @@ class HtmlFormatter { // XML code paths if possible and fix there. $html = str_replace( ' ', '', $html ); } - wfProfileOut( __METHOD__ . '-fixes' ); } else { $html = $this->html; } @@ -302,14 +292,11 @@ class HtmlFormatter { $html = preg_replace( '/<!--.*?-->|^.*?<body>|<\/body>.*$/s', '', $html ); $html = $this->onHtmlReady( $html ); - wfProfileIn( __METHOD__ . '-flatten' ); if ( $this->elementsToFlatten ) { $elements = implode( '|', $this->elementsToFlatten ); $html = preg_replace( "#</?($elements)\\b[^>]*>#is", '', $html ); } - wfProfileOut( __METHOD__ . '-flatten' ); - wfProfileOut( __METHOD__ ); return $html; } @@ -322,6 +309,7 @@ class HtmlFormatter { * @param string $type The type of selector (ID, CLASS, TAG_CLASS, or TAG) * @param string $rawName The raw name of the selector * @return bool Whether the selector was successfully recognised + * @throws MWException */ protected function parseSelector( $selector, &$type, &$rawName ) { if ( strpos( $selector, '.' ) === 0 ) { @@ -349,7 +337,6 @@ class HtmlFormatter { * @return array */ protected function parseItemsToRemove() { - wfProfileIn( __METHOD__ ); $removals = array( 'ID' => array(), 'TAG' => array(), @@ -371,7 +358,6 @@ class HtmlFormatter { $removals['TAG'][] = 'video'; } - wfProfileOut( __METHOD__ ); return $removals; } } diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php index 83021245..8e05f597 100644 --- a/includes/HttpFunctions.php +++ b/includes/HttpFunctions.php @@ -55,11 +55,11 @@ class Http { * to avoid attacks on intranet services accessible by HTTP. * - userAgent A user agent, if you want to override the default * MediaWiki/$wgVersion + * @param string $caller The method making this request, for profiling * @return string|bool (bool)false on failure or a string on success */ - public static function request( $method, $url, $options = array() ) { + public static function request( $method, $url, $options = array(), $caller = __METHOD__ ) { wfDebug( "HTTP: $method: $url\n" ); - wfProfileIn( __METHOD__ . "-$method" ); $options['method'] = strtoupper( $method ); @@ -70,29 +70,39 @@ class Http { $options['connectTimeout'] = 'default'; } - $req = MWHttpRequest::factory( $url, $options ); + $req = MWHttpRequest::factory( $url, $options, $caller ); $status = $req->execute(); $content = false; if ( $status->isOK() ) { $content = $req->getContent(); } - wfProfileOut( __METHOD__ . "-$method" ); return $content; } /** * Simple wrapper for Http::request( 'GET' ) * @see Http::request() + * @since 1.25 Second parameter $timeout removed. Second parameter + * is now $options which can be given a 'timeout' * * @param string $url - * @param string $timeout * @param array $options + * @param string $caller The method making this request, for profiling * @return string */ - public static function get( $url, $timeout = 'default', $options = array() ) { - $options['timeout'] = $timeout; - return Http::request( 'GET', $url, $options ); + public static function get( $url, $options = array(), $caller = __METHOD__ ) { + $args = func_get_args(); + if ( isset( $args[1] ) && ( is_string( $args[1] ) || is_numeric( $args[1] ) ) ) { + // Second was used to be the timeout + // And third parameter used to be $options + wfWarn( "Second parameter should not be a timeout.", 2 ); + $options = isset( $args[2] ) && is_array( $args[2] ) ? + $args[2] : array(); + $options['timeout'] = $args[1]; + $caller = __METHOD__; + } + return Http::request( 'GET', $url, $options, $caller ); } /** @@ -101,10 +111,11 @@ class Http { * * @param string $url * @param array $options + * @param string $caller The method making this request, for profiling * @return string */ - public static function post( $url, $options = array() ) { - return Http::request( 'POST', $url, $options ); + public static function post( $url, $options = array(), $caller = __METHOD__ ) { + return Http::request( 'POST', $url, $options, $caller ); } /** @@ -114,7 +125,7 @@ class Http { * @return bool */ public static function isLocalURL( $url ) { - global $wgCommandLineMode, $wgConf; + global $wgCommandLineMode, $wgLocalVirtualHosts, $wgConf; if ( $wgCommandLineMode ) { return false; @@ -126,7 +137,7 @@ class Http { $host = $matches[1]; // Split up dotwise $domainParts = explode( '.', $host ); - // Check if this domain or any superdomain is listed in $wgConf as a local virtual host + // Check if this domain or any superdomain is listed as a local virtual host $domainParts = array_reverse( $domainParts ); $domain = ''; @@ -139,7 +150,9 @@ class Http { $domain = $domainPart . '.' . $domain; } - if ( $wgConf->isLocalVHost( $domain ) ) { + if ( in_array( $domain, $wgLocalVirtualHosts ) + || $wgConf->isLocalVHost( $domain ) + ) { return true; } } @@ -217,10 +230,22 @@ class MWHttpRequest { public $status; /** + * @var Profiler + */ + protected $profiler; + + /** + * @var string + */ + protected $profileName; + + /** * @param string $url Url to use. If protocol-relative, will be expanded to an http:// URL * @param array $options (optional) extra params to pass (see Http::request()) + * @param string $caller The method making this request, for profiling + * @param Profiler $profiler An instance of the profiler for profiling, or null */ - protected function __construct( $url, $options = array() ) { + protected function __construct( $url, $options = array(), $caller = __METHOD__, $profiler = null ) { global $wgHTTPTimeout, $wgHTTPConnectTimeout; $this->url = wfExpandUrl( $url, PROTO_HTTP ); @@ -263,6 +288,10 @@ class MWHttpRequest { if ( $this->noProxy ) { $this->proxy = ''; // noProxy takes precedence } + + // Profile based on what's calling us + $this->profiler = $profiler; + $this->profileName = $caller; } /** @@ -278,11 +307,12 @@ class MWHttpRequest { * Generate a new request object * @param string $url Url to use * @param array $options (optional) extra params to pass (see Http::request()) + * @param string $caller The method making this request, for profiling * @throws MWException * @return CurlHttpRequest|PhpHttpRequest * @see MWHttpRequest::__construct */ - public static function factory( $url, $options = null ) { + public static function factory( $url, $options = null, $caller = __METHOD__ ) { if ( !Http::$httpEngine ) { Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php'; } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) { @@ -292,7 +322,7 @@ class MWHttpRequest { switch ( Http::$httpEngine ) { case 'curl': - return new CurlHttpRequest( $url, $options ); + return new CurlHttpRequest( $url, $options, $caller, Profiler::instance() ); case 'php': if ( !wfIniGetBool( 'allow_url_fopen' ) ) { throw new MWException( __METHOD__ . ': allow_url_fopen ' . @@ -301,7 +331,7 @@ class MWHttpRequest { 'http://php.net/curl.' ); } - return new PhpHttpRequest( $url, $options ); + return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() ); default: throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' ); } @@ -434,7 +464,6 @@ class MWHttpRequest { * @return Status */ public function execute() { - wfProfileIn( __METHOD__ ); $this->content = ""; @@ -452,7 +481,6 @@ class MWHttpRequest { $this->setUserAgent( Http::userAgent() ); } - wfProfileOut( __METHOD__ ); } /** @@ -461,7 +489,6 @@ class MWHttpRequest { * found in an array in the member variable headerList. */ protected function parseHeader() { - wfProfileIn( __METHOD__ ); $lastname = ""; @@ -480,7 +507,6 @@ class MWHttpRequest { $this->parseCookies(); - wfProfileOut( __METHOD__ ); } /** @@ -614,7 +640,6 @@ class MWHttpRequest { * Parse the cookies in the response headers and store them in the cookie jar. */ protected function parseCookies() { - wfProfileIn( __METHOD__ ); if ( !$this->cookieJar ) { $this->cookieJar = new CookieJar; @@ -627,7 +652,6 @@ class MWHttpRequest { } } - wfProfileOut( __METHOD__ ); } /** @@ -715,12 +739,10 @@ class CurlHttpRequest extends MWHttpRequest { } public function execute() { - wfProfileIn( __METHOD__ ); parent::execute(); if ( !$this->status->isOK() ) { - wfProfileOut( __METHOD__ ); return $this->status; } @@ -766,7 +788,6 @@ class CurlHttpRequest extends MWHttpRequest { $curlHandle = curl_init( $this->url ); if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) { - wfProfileOut( __METHOD__ ); throw new MWException( "Error setting curl options." ); } @@ -781,6 +802,12 @@ class CurlHttpRequest extends MWHttpRequest { wfRestoreWarnings(); } + if ( $this->profiler ) { + $profileSection = $this->profiler->scopedProfileIn( + __METHOD__ . '-' . $this->profileName + ); + } + $curlRes = curl_exec( $curlHandle ); if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) { $this->status->fatal( 'http-timed-out', $this->url ); @@ -792,11 +819,13 @@ class CurlHttpRequest extends MWHttpRequest { curl_close( $curlHandle ); + if ( $this->profiler ) { + $this->profiler->scopedProfileOut( $profileSection ); + } + $this->parseHeader(); $this->setStatus(); - wfProfileOut( __METHOD__ ); - return $this->status; } @@ -832,7 +861,6 @@ class PhpHttpRequest extends MWHttpRequest { } public function execute() { - wfProfileIn( __METHOD__ ); parent::execute(); @@ -903,6 +931,11 @@ class PhpHttpRequest extends MWHttpRequest { $result = array(); + if ( $this->profiler ) { + $profileSection = $this->profiler->scopedProfileIn( + __METHOD__ . '-' . $this->profileName + ); + } do { $reqCount++; wfSuppressWarnings(); @@ -933,18 +966,19 @@ class PhpHttpRequest extends MWHttpRequest { break; } } while ( true ); + if ( $this->profiler ) { + $this->profiler->scopedProfileOut( $profileSection ); + } $this->setStatus(); if ( $fh === false ) { $this->status->fatal( 'http-request-error' ); - wfProfileOut( __METHOD__ ); return $this->status; } if ( $result['timed_out'] ) { $this->status->fatal( 'http-timed-out', $this->url ); - wfProfileOut( __METHOD__ ); return $this->status; } @@ -966,8 +1000,6 @@ class PhpHttpRequest extends MWHttpRequest { } fclose( $fh ); - wfProfileOut( __METHOD__ ); - return $this->status; } } diff --git a/includes/Import.php b/includes/Import.php index 5319076e..d31be43b 100644 --- a/includes/Import.php +++ b/includes/Import.php @@ -32,18 +32,31 @@ */ class WikiImporter { private $reader = null; + private $foreignNamespaces = null; private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback; - private $mSiteInfoCallback, $mTargetNamespace, $mTargetRootPage, $mPageOutCallback; + private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback; private $mNoticeCallback, $mDebug; private $mImportUploads, $mImageBasePath; private $mNoUpdates = false; + /** @var Config */ + private $config; + /** @var ImportTitleFactory */ + private $importTitleFactory; + /** @var array */ + private $countableCache = array(); /** * Creates an ImportXMLReader drawing from the source provided - * @param ImportStreamSource $source + * @param ImportSource $source + * @param Config $config */ - function __construct( ImportStreamSource $source ) { + function __construct( ImportSource $source, Config $config = null ) { $this->reader = new XMLReader(); + if ( !$config ) { + wfDeprecated( __METHOD__ . ' without a Config instance', '1.25' ); + $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); + } + $this->config = $config; if ( !in_array( 'uploadsource', stream_get_wrappers() ) ) { stream_wrapper_register( 'uploadsource', 'UploadSourceAdapter' ); @@ -56,10 +69,13 @@ class WikiImporter { } // Default callbacks + $this->setPageCallback( array( $this, 'beforeImportPage' ) ); $this->setRevisionCallback( array( $this, "importRevision" ) ); $this->setUploadCallback( array( $this, 'importUpload' ) ); $this->setLogItemCallback( array( $this, 'importLogItem' ) ); $this->setPageOutCallback( array( $this, 'finishImportPage' ) ); + + $this->importTitleFactory = new NaiveImportTitleFactory(); } /** @@ -192,6 +208,15 @@ class WikiImporter { } /** + * Sets the factory object to use to convert ForeignTitle objects into local + * Title objects + * @param ImportTitleFactory $factory + */ + public function setImportTitleFactory( $factory ) { + $this->importTitleFactory = $factory; + } + + /** * Set a target namespace to override the defaults * @param null|int $namespace * @return bool @@ -200,9 +225,16 @@ class WikiImporter { if ( is_null( $namespace ) ) { // Don't override namespaces $this->mTargetNamespace = null; - } elseif ( $namespace >= 0 ) { - // @todo FIXME: Check for validity - $this->mTargetNamespace = intval( $namespace ); + $this->setImportTitleFactory( new NaiveImportTitleFactory() ); + return true; + } elseif ( + $namespace >= 0 && + MWNamespace::exists( intval( $namespace ) ) + ) { + $namespace = intval( $namespace ); + $this->mTargetNamespace = $namespace; + $this->setImportTitleFactory( new NamespaceImportTitleFactory( $namespace ) ); + return true; } else { return false; } @@ -217,7 +249,7 @@ class WikiImporter { $status = Status::newGood(); if ( is_null( $rootpage ) ) { // No rootpage - $this->mTargetRootPage = null; + $this->setImportTitleFactory( new NaiveImportTitleFactory() ); } elseif ( $rootpage !== '' ) { $rootpage = rtrim( $rootpage, '/' ); //avoid double slashes $title = Title::newFromText( $rootpage, !is_null( $this->mTargetNamespace ) @@ -236,9 +268,9 @@ class WikiImporter { : $wgContLang->getNsText( $title->getNamespace() ); $status->fatal( 'import-rootpage-nosubpage', $displayNSText ); } else { - // set namespace to 'all', so the namespace check in processTitle() can passed + // set namespace to 'all', so the namespace check in processTitle() can pass $this->setTargetNamespace( null ); - $this->mTargetRootPage = $title->getPrefixedDBkey(); + $this->setImportTitleFactory( new SubpageImportTitleFactory( $title ) ); } } } @@ -260,6 +292,19 @@ class WikiImporter { } /** + * Default per-page callback. Sets up some things related to site statistics + * @param array $titleAndForeignTitle Two-element array, with Title object at + * index 0 and ForeignTitle object at index 1 + * @return bool + */ + public function beforeImportPage( $titleAndForeignTitle ) { + $title = $titleAndForeignTitle[0]; + $page = WikiPage::factory( $title ); + $this->countableCache['title_' . $title->getPrefixedText()] = $page->isCountable(); + return true; + } + + /** * Default per-revision callback, performs the import. * @param WikiRevision $revision * @return bool @@ -312,15 +357,41 @@ class WikiImporter { /** * Mostly for hook use * @param Title $title - * @param string $origTitle + * @param ForeignTitle $foreignTitle * @param int $revCount * @param int $sRevCount * @param array $pageInfo * @return bool */ - public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) { + public function finishImportPage( $title, $foreignTitle, $revCount, + $sRevCount, $pageInfo ) { + + // Update article count statistics (T42009) + // The normal counting logic in WikiPage->doEditUpdates() is designed for + // one-revision-at-a-time editing, not bulk imports. In this situation it + // suffers from issues of slave lag. We let WikiPage handle the total page + // and revision count, and we implement our own custom logic for the + // article (content page) count. + $page = WikiPage::factory( $title ); + $page->loadPageData( 'fromdbmaster' ); + $content = $page->getContent(); + if ( $content === null ) { + wfDebug( __METHOD__ . ': Skipping article count adjustment for ' . $title . + ' because WikiPage::getContent() returned null' ); + } else { + $editInfo = $page->prepareContentForEdit( $content ); + $countKey = 'title_' . $title->getPrefixedText(); + $countable = $page->isCountable( $editInfo ); + if ( array_key_exists( $countKey, $this->countableCache ) && + $countable != $this->countableCache[ $countKey ] ) { + DeferredUpdates::addUpdate( SiteStatsUpdate::factory( array( + 'articles' => ( (int)$countable - (int)$this->countableCache[ $countKey ] ) + ) ) ); + } + } + $args = func_get_args(); - return wfRunHooks( 'AfterImportPage', $args ); + return Hooks::run( 'AfterImportPage', $args ); } /** @@ -341,6 +412,20 @@ class WikiImporter { } /** + * Notify the callback function of site info + * @param array $siteInfo + * @return bool|mixed + */ + private function siteInfoCallback( $siteInfo ) { + if ( isset( $this->mSiteInfoCallback ) ) { + return call_user_func_array( $this->mSiteInfoCallback, + array( $siteInfo, $this ) ); + } else { + return false; + } + } + + /** * Notify the callback function when a new "<page>" is reached. * @param Title $title */ @@ -353,12 +438,13 @@ class WikiImporter { /** * Notify the callback function when a "</page>" is closed. * @param Title $title - * @param Title $origTitle + * @param ForeignTitle $foreignTitle * @param int $revCount * @param int $sucCount Number of revisions for which callback returned true * @param array $pageInfo Associative array of page information */ - private function pageOutCallback( $title, $origTitle, $revCount, $sucCount, $pageInfo ) { + private function pageOutCallback( $title, $foreignTitle, $revCount, + $sucCount, $pageInfo ) { if ( isset( $this->mPageOutCallback ) ) { $args = func_get_args(); call_user_func_array( $this->mPageOutCallback, $args ); @@ -396,7 +482,8 @@ class WikiImporter { /** * Retrieves the contents of the named attribute of the current element. * @param string $attr The name of the attribute - * @return string The value of the attribute or an empty string if it is not set in the current element. + * @return string The value of the attribute or an empty string if it is not set in the current + * element. */ public function nodeAttribute( $attr ) { return $this->reader->getAttribute( $attr ); @@ -416,11 +503,11 @@ class WikiImporter { $buffer = ""; while ( $this->reader->read() ) { switch ( $this->reader->nodeType ) { - case XmlReader::TEXT: - case XmlReader::SIGNIFICANT_WHITESPACE: + case XMLReader::TEXT: + case XMLReader::SIGNIFICANT_WHITESPACE: $buffer .= $this->reader->value; break; - case XmlReader::END_ELEMENT: + case XMLReader::END_ELEMENT: return $buffer; } } @@ -452,51 +539,76 @@ class WikiImporter { $keepReading = $this->reader->read(); $skip = false; - while ( $keepReading ) { - $tag = $this->reader->name; - $type = $this->reader->nodeType; - - if ( !wfRunHooks( 'ImportHandleToplevelXMLTag', array( $this ) ) ) { - // Do nothing - } elseif ( $tag == 'mediawiki' && $type == XmlReader::END_ELEMENT ) { - break; - } elseif ( $tag == 'siteinfo' ) { - $this->handleSiteInfo(); - } elseif ( $tag == 'page' ) { - $this->handlePage(); - } elseif ( $tag == 'logitem' ) { - $this->handleLogItem(); - } elseif ( $tag != '#text' ) { - $this->warn( "Unhandled top-level XML tag $tag" ); - - $skip = true; - } + $rethrow = null; + try { + while ( $keepReading ) { + $tag = $this->reader->name; + $type = $this->reader->nodeType; + + if ( !Hooks::run( 'ImportHandleToplevelXMLTag', array( $this ) ) ) { + // Do nothing + } elseif ( $tag == 'mediawiki' && $type == XMLReader::END_ELEMENT ) { + break; + } elseif ( $tag == 'siteinfo' ) { + $this->handleSiteInfo(); + } elseif ( $tag == 'page' ) { + $this->handlePage(); + } elseif ( $tag == 'logitem' ) { + $this->handleLogItem(); + } elseif ( $tag != '#text' ) { + $this->warn( "Unhandled top-level XML tag $tag" ); + + $skip = true; + } - if ( $skip ) { - $keepReading = $this->reader->next(); - $skip = false; - $this->debug( "Skip" ); - } else { - $keepReading = $this->reader->read(); + if ( $skip ) { + $keepReading = $this->reader->next(); + $skip = false; + $this->debug( "Skip" ); + } else { + $keepReading = $this->reader->read(); + } } + } catch ( Exception $ex ) { + $rethrow = $ex; } + // finally libxml_disable_entity_loader( $oldDisable ); + $this->reader->close(); + + if ( $rethrow ) { + throw $rethrow; + } + return true; } - /** - * @return bool - * @throws MWException - */ private function handleSiteInfo() { - // Site info is useful, but not actually used for dump imports. - // Includes a quick short-circuit to save performance. - if ( !$this->mSiteInfoCallback ) { - $this->reader->next(); - return true; + $this->debug( "Enter site info handler." ); + $siteInfo = array(); + + // Fields that can just be stuffed in the siteInfo object + $normalFields = array( 'sitename', 'base', 'generator', 'case' ); + + while ( $this->reader->read() ) { + if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + $this->reader->name == 'siteinfo' ) { + break; + } + + $tag = $this->reader->name; + + if ( $tag == 'namespace' ) { + $this->foreignNamespaces[ $this->nodeAttribute( 'key' ) ] = + $this->nodeContents(); + } elseif ( in_array( $tag, $normalFields ) ) { + $siteInfo[$tag] = $this->nodeContents(); + } } - throw new MWException( "SiteInfo tag is not yet handled, do not set mSiteInfoCallback" ); + + $siteInfo['_namespaces'] = $this->foreignNamespaces; + $this->siteInfoCallback( $siteInfo ); } private function handleLogItem() { @@ -508,14 +620,14 @@ class WikiImporter { 'logtitle', 'params' ); while ( $this->reader->read() ) { - if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + if ( $this->reader->nodeType == XMLReader::END_ELEMENT && $this->reader->name == 'logitem' ) { break; } $tag = $this->reader->name; - if ( !wfRunHooks( 'ImportHandleLogItemXMLTag', array( + if ( !Hooks::run( 'ImportHandleLogItemXMLTag', array( $this, $logInfo ) ) ) { // Do nothing @@ -536,7 +648,7 @@ class WikiImporter { * @return bool|mixed */ private function processLogItem( $logInfo ) { - $revision = new WikiRevision; + $revision = new WikiRevision( $this->config ); $revision->setID( $logInfo['id'] ); $revision->setType( $logInfo['type'] ); @@ -566,23 +678,25 @@ class WikiImporter { $pageInfo = array( 'revisionCount' => 0, 'successfulRevisionCount' => 0 ); // Fields that can just be stuffed in the pageInfo object - $normalFields = array( 'title', 'id', 'redirect', 'restrictions' ); + $normalFields = array( 'title', 'ns', 'id', 'redirect', 'restrictions' ); $skip = false; $badTitle = false; while ( $skip ? $this->reader->next() : $this->reader->read() ) { - if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + if ( $this->reader->nodeType == XMLReader::END_ELEMENT && $this->reader->name == 'page' ) { break; } + $skip = false; + $tag = $this->reader->name; if ( $badTitle ) { // The title is invalid, bail out of this page $skip = true; - } elseif ( !wfRunHooks( 'ImportHandlePageXMLTag', array( $this, + } elseif ( !Hooks::run( 'ImportHandlePageXMLTag', array( $this, &$pageInfo ) ) ) { // Do nothing } elseif ( in_array( $tag, $normalFields ) ) { @@ -597,29 +711,35 @@ class WikiImporter { $pageInfo[$tag] = $this->nodeAttribute( 'title' ); } else { $pageInfo[$tag] = $this->nodeContents(); - if ( $tag == 'title' ) { - $title = $this->processTitle( $pageInfo['title'] ); + } + } elseif ( $tag == 'revision' || $tag == 'upload' ) { + if ( !isset( $title ) ) { + $title = $this->processTitle( $pageInfo['title'], + isset( $pageInfo['ns'] ) ? $pageInfo['ns'] : null ); + + if ( !$title ) { + $badTitle = true; + $skip = true; + } - if ( !$title ) { - $badTitle = true; - $skip = true; - } + $this->pageCallback( $title ); + list( $pageInfo['_title'], $foreignTitle ) = $title; + } - $this->pageCallback( $title ); - list( $pageInfo['_title'], $origTitle ) = $title; + if ( $title ) { + if ( $tag == 'revision' ) { + $this->handleRevision( $pageInfo ); + } else { + $this->handleUpload( $pageInfo ); } } - } elseif ( $tag == 'revision' ) { - $this->handleRevision( $pageInfo ); - } elseif ( $tag == 'upload' ) { - $this->handleUpload( $pageInfo ); } elseif ( $tag != '#text' ) { $this->warn( "Unhandled page XML tag $tag" ); $skip = true; } } - $this->pageOutCallback( $pageInfo['_title'], $origTitle, + $this->pageOutCallback( $pageInfo['_title'], $foreignTitle, $pageInfo['revisionCount'], $pageInfo['successfulRevisionCount'], $pageInfo ); @@ -637,14 +757,14 @@ class WikiImporter { $skip = false; while ( $skip ? $this->reader->next() : $this->reader->read() ) { - if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + if ( $this->reader->nodeType == XMLReader::END_ELEMENT && $this->reader->name == 'revision' ) { break; } $tag = $this->reader->name; - if ( !wfRunHooks( 'ImportHandleRevisionXMLTag', array( + if ( !Hooks::run( 'ImportHandleRevisionXMLTag', array( $this, $pageInfo, $revisionInfo ) ) ) { // Do nothing @@ -670,7 +790,7 @@ class WikiImporter { * @return bool|mixed */ private function processRevision( $pageInfo, $revisionInfo ) { - $revision = new WikiRevision; + $revision = new WikiRevision( $this->config ); if ( isset( $revisionInfo['id'] ) ) { $revision->setID( $revisionInfo['id'] ); @@ -729,14 +849,14 @@ class WikiImporter { $skip = false; while ( $skip ? $this->reader->next() : $this->reader->read() ) { - if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + if ( $this->reader->nodeType == XMLReader::END_ELEMENT && $this->reader->name == 'upload' ) { break; } $tag = $this->reader->name; - if ( !wfRunHooks( 'ImportHandleUploadXMLTag', array( + if ( !Hooks::run( 'ImportHandleUploadXMLTag', array( $this, $pageInfo ) ) ) { // Do nothing @@ -786,7 +906,7 @@ class WikiImporter { * @return mixed */ private function processUpload( $pageInfo, $uploadInfo ) { - $revision = new WikiRevision; + $revision = new WikiRevision( $this->config ); $text = isset( $uploadInfo['text'] ) ? $uploadInfo['text'] : ''; $revision->setTitle( $pageInfo['_title'] ); @@ -827,7 +947,7 @@ class WikiImporter { $info = array(); while ( $this->reader->read() ) { - if ( $this->reader->nodeType == XmlReader::END_ELEMENT && + if ( $this->reader->nodeType == XMLReader::END_ELEMENT && $this->reader->name == 'contributor' ) { break; } @@ -844,29 +964,27 @@ class WikiImporter { /** * @param string $text + * @param string|null $ns * @return array|bool */ - private function processTitle( $text ) { - global $wgCommandLineMode; - - $workTitle = $text; - $origTitle = Title::newFromText( $workTitle ); - - if ( !is_null( $this->mTargetNamespace ) && !is_null( $origTitle ) ) { - # makeTitleSafe, because $origTitle can have a interwiki (different setting of interwiki map) - # and than dbKey can begin with a lowercase char - $title = Title::makeTitleSafe( $this->mTargetNamespace, - $origTitle->getDBkey() ); + private function processTitle( $text, $ns = null ) { + if ( is_null( $this->foreignNamespaces ) ) { + $foreignTitleFactory = new NaiveForeignTitleFactory(); } else { - if ( !is_null( $this->mTargetRootPage ) ) { - $workTitle = $this->mTargetRootPage . '/' . $workTitle; - } - $title = Title::newFromText( $workTitle ); + $foreignTitleFactory = new NamespaceAwareForeignTitleFactory( + $this->foreignNamespaces ); } + $foreignTitle = $foreignTitleFactory->createForeignTitle( $text, + intval( $ns ) ); + + $title = $this->importTitleFactory->createTitleFromForeignTitle( + $foreignTitle ); + + $commandLineMode = $this->config->get( 'CommandLineMode' ); if ( is_null( $title ) ) { # Invalid page title? Ignore the page - $this->notice( 'import-error-invalid', $workTitle ); + $this->notice( 'import-error-invalid', $foreignTitle->getFullText() ); return false; } elseif ( $title->isExternal() ) { $this->notice( 'import-error-interwiki', $title->getPrefixedText() ); @@ -874,17 +992,17 @@ class WikiImporter { } elseif ( !$title->canExist() ) { $this->notice( 'import-error-special', $title->getPrefixedText() ); return false; - } elseif ( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) { + } elseif ( !$title->userCan( 'edit' ) && !$commandLineMode ) { # Do not import if the importing wiki user cannot edit this page $this->notice( 'import-error-edit', $title->getPrefixedText() ); return false; - } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) { + } elseif ( !$title->exists() && !$title->userCan( 'create' ) && !$commandLineMode ) { # Do not import if the importing wiki user cannot create this page $this->notice( 'import-error-create', $title->getPrefixedText() ); return false; } - return array( $title, $origTitle ); + return array( $title, $foreignTitle ); } } @@ -903,10 +1021,10 @@ class UploadSourceAdapter { private $mPosition; /** - * @param ImportStreamSource $source + * @param ImportSource $source * @return string */ - static function registerSource( ImportStreamSource $source ) { + static function registerSource( ImportSource $source ) { $id = wfRandomString(); self::$sourceRegistrations[$id] = $source; @@ -1093,6 +1211,13 @@ class WikiRevision { /** @var bool */ private $mNoUpdates = false; + /** @var Config $config */ + private $config; + + public function __construct( Config $config ) { + $this->config = $config; + } + /** * @param Title $title * @throws MWException @@ -1434,8 +1559,7 @@ class WikiRevision { } // avoid memory leak...? - $linkCache = LinkCache::singleton(); - $linkCache->clear(); + Title::clearCaches(); $page = WikiPage::factory( $this->title ); $page->loadPageData( 'fromdbmaster' ); @@ -1461,7 +1585,6 @@ class WikiRevision { $this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" ); return false; } - $oldcountable = $page->isCountable(); } # @todo FIXME: Use original rev_id optionally (better for backups) @@ -1484,10 +1607,11 @@ class WikiRevision { if ( $changed !== false && !$this->mNoUpdates ) { wfDebug( __METHOD__ . ": running updates\n" ); + // countable/oldcountable stuff is handled in WikiImporter::finishImportPage $page->doEditUpdates( $revision, $userObj, - array( 'created' => $created, 'oldcountable' => $oldcountable ) + array( 'created' => $created, 'oldcountable' => 'no-change' ) ); } @@ -1550,6 +1674,7 @@ class WikiRevision { RepoGroup::singleton()->getLocalRepo(), $archiveName ); } else { $file = wfLocalFile( $this->getTitle() ); + $file->load( File::READ_LATEST ); wfDebug( __METHOD__ . 'Importing new file as ' . $file->getName() . "\n" ); if ( $file->exists() && $file->getTimestamp() > $this->getTimestamp() ) { $archiveName = $file->getTimestamp() . '!' . $file->getName(); @@ -1599,7 +1724,7 @@ class WikiRevision { wfDebug( __METHOD__ . ": Successful\n" ); return true; } else { - wfDebug( __METHOD__ . ': failed: ' . $status->getXml() . "\n" ); + wfDebug( __METHOD__ . ': failed: ' . $status->getHTML() . "\n" ); return false; } } @@ -1608,8 +1733,7 @@ class WikiRevision { * @return bool|string */ function downloadSource() { - global $wgEnableUploads; - if ( !$wgEnableUploads ) { + if ( !$this->config->get( 'EnableUploads' ) ) { return false; } @@ -1622,7 +1746,7 @@ class WikiRevision { // @todo FIXME! $src = $this->getSrc(); - $data = Http::get( $src ); + $data = Http::get( $src, array(), __METHOD__ ); if ( !$data ) { wfDebug( "IMPORT: couldn't fetch source $src\n" ); fclose( $f ); @@ -1639,10 +1763,37 @@ class WikiRevision { } /** - * @todo document (e.g. one-sentence class description). + * Source interface for XML import. + */ +interface ImportSource { + + /** + * Indicates whether the end of the input has been reached. + * Will return true after a finite number of calls to readChunk. + * + * @return bool true if there is no more input, false otherwise. + */ + function atEnd(); + + /** + * Return a chunk of the input, as a (possibly empty) string. + * When the end of input is reached, readChunk() returns false. + * If atEnd() returns false, readChunk() will return a string. + * If atEnd() returns true, readChunk() will return false. + * + * @return bool|string + */ + function readChunk(); +} + +/** + * Used for importing XML dumps where the content of the dump is in a string. + * This class is ineffecient, and should only be used for small dumps. + * For larger dumps, ImportStreamSource should be used instead. + * * @ingroup SpecialPage */ -class ImportStringSource { +class ImportStringSource implements ImportSource { function __construct( $string ) { $this->mString = $string; $this->mRead = false; @@ -1668,10 +1819,10 @@ class ImportStringSource { } /** - * @todo document (e.g. one-sentence class description). + * Imports a XML dump from a file (either from file upload, files on disk, or HTTP) * @ingroup SpecialPage */ -class ImportStreamSource { +class ImportStreamSource implements ImportSource { function __construct( $handle ) { $this->mHandle = $handle; } @@ -1752,7 +1903,7 @@ class ImportStreamSource { # quicker and sorts out user-agent problems which might # otherwise prevent importing from large sites, such # as the Wikimedia cluster, etc. - $data = Http::request( $method, $url, array( 'followRedirects' => true ) ); + $data = Http::request( $method, $url, array( 'followRedirects' => true ), __METHOD__ ); if ( $data !== false ) { $file = tmpfile(); fwrite( $file, $data ); diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php index 340ae8f3..99aaaa09 100644 --- a/includes/LinkFilter.php +++ b/includes/LinkFilter.php @@ -92,7 +92,7 @@ class LinkFilter { * @return array Array to be passed to DatabaseBase::buildLike() or false on error */ public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) { - $db = wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_SLAVE ); $target = $protocol . $filterEntry; $bits = wfParseUrl( $target ); diff --git a/includes/Linker.php b/includes/Linker.php index be850d02..b58dabab 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -37,23 +37,10 @@ class Linker { const TOOL_LINKS_EMAIL = 2; /** - * Get the appropriate HTML attributes to add to the "a" element of an - * external link, as created by [wikisyntax]. - * - * @param string $class The contents of the class attribute; if an empty - * string is passed, which is the default value, defaults to 'external'. - * @return string - * @deprecated since 1.18 Just pass the external class directly to something - * using Html::expandAttributes. - */ - static function getExternalLinkAttributes( $class = 'external' ) { - wfDeprecated( __METHOD__, '1.18' ); - return self::getLinkAttributesInternal( '', $class ); - } - - /** * Get the appropriate HTML attributes to add to the "a" element of an interwiki link. * + * @deprecated since 1.25 + * * @param string $title The title text for the link, URL-encoded (???) but * not HTML-escaped * @param string $unused Unused @@ -64,6 +51,8 @@ class Linker { static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) { global $wgContLang; + wfDeprecated( __METHOD__, '1.25' ); + # @todo FIXME: We have a whole bunch of handling here that doesn't happen in # getExternalLinkAttributes, why? $title = urldecode( $title ); @@ -76,6 +65,8 @@ class Linker { /** * Get the appropriate HTML attributes to add to the "a" element of an internal link. * + * @deprecated since 1.25 + * * @param string $title The title text for the link, URL-encoded (???) but * not HTML-escaped * @param string $unused Unused @@ -83,6 +74,8 @@ class Linker { * @return string */ static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) { + wfDeprecated( __METHOD__, '1.25' ); + $title = urldecode( $title ); $title = str_replace( '_', ' ', $title ); return self::getLinkAttributesInternal( $title, $class ); @@ -92,6 +85,8 @@ class Linker { * Get the appropriate HTML attributes to add to the "a" element of an internal * link, given the Title object for the page we want to link to. * + * @deprecated since 1.25 + * * @param Title $nt * @param string $unused Unused * @param string $class The contents of the class attribute, default none @@ -100,6 +95,8 @@ class Linker { * @return string */ static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) { + wfDeprecated( __METHOD__, '1.25' ); + if ( $title === false ) { $title = $nt->getPrefixedText(); } @@ -109,12 +106,16 @@ class Linker { /** * Common code for getLinkAttributesX functions * + * @deprecated since 1.25 + * * @param string $title * @param string $class * * @return string */ private static function getLinkAttributesInternal( $title, $class ) { + wfDeprecated( __METHOD__, '1.25' ); + $title = htmlspecialchars( $title ); $class = htmlspecialchars( $class ); $r = ''; @@ -193,10 +194,9 @@ class Linker { $target, $html = null, $customAttribs = array(), $query = array(), $options = array() ) { if ( !$target instanceof Title ) { - wfWarn( __METHOD__ . ': Requires $target to be a Title object.' ); + wfWarn( __METHOD__ . ': Requires $target to be a Title object.', 2 ); return "<!-- ERROR -->$html"; } - wfProfileIn( __METHOD__ ); if ( is_string( $query ) ) { // some functions withing core using this still hand over query strings @@ -208,9 +208,9 @@ class Linker { $dummy = new DummyLinker; // dummy linker instance for bc on the hooks $ret = null; - if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html, - &$customAttribs, &$query, &$options, &$ret ) ) ) { - wfProfileOut( __METHOD__ ); + if ( !Hooks::run( 'LinkBegin', + array( $dummy, $target, &$html, &$customAttribs, &$query, &$options, &$ret ) ) + ) { return $ret; } @@ -218,15 +218,13 @@ class Linker { $target = self::normaliseSpecialPage( $target ); # If we don't know whether the page exists, let's find out. - wfProfileIn( __METHOD__ . '-checkPageExistence' ); - if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) { + if ( !in_array( 'known', $options ) && !in_array( 'broken', $options ) ) { if ( $target->isKnown() ) { $options[] = 'known'; } else { $options[] = 'broken'; } } - wfProfileOut( __METHOD__ . '-checkPageExistence' ); $oldquery = array(); if ( in_array( "forcearticlepath", $options ) && $query ) { @@ -249,11 +247,10 @@ class Linker { } $ret = null; - if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) { + if ( Hooks::run( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) { $ret = Html::rawElement( 'a', $attribs, $html ); } - wfProfileOut( __METHOD__ ); return $ret; } @@ -278,7 +275,6 @@ class Linker { * @return string */ private static function linkUrl( $target, $query, $options ) { - wfProfileIn( __METHOD__ ); # We don't want to include fragments for broken links, because they # generally make no sense. if ( in_array( 'broken', $options ) && $target->hasFragment() ) { @@ -304,7 +300,6 @@ class Linker { } $ret = $target->getLinkURL( $query, false, $proto ); - wfProfileOut( __METHOD__ ); return $ret; } @@ -318,12 +313,10 @@ class Linker { * @return array */ private static function linkAttribs( $target, $attribs, $options ) { - wfProfileIn( __METHOD__ ); global $wgUser; $defaults = array(); if ( !in_array( 'noclasses', $options ) ) { - wfProfileIn( __METHOD__ . '-getClasses' ); # Now build the classes. $classes = array(); @@ -344,7 +337,6 @@ class Linker { if ( $classes != array() ) { $defaults['class'] = implode( ' ', $classes ); } - wfProfileOut( __METHOD__ . '-getClasses' ); } # Get a default title attribute. @@ -364,11 +356,10 @@ class Linker { foreach ( $merged as $key => $val ) { # A false value suppresses the attribute, and we don't want the # href attribute to be overridden. - if ( $key != 'href' and $val !== false ) { + if ( $key != 'href' && $val !== false ) { $ret[$key] = $val; } } - wfProfileOut( __METHOD__ ); return $ret; } @@ -409,7 +400,7 @@ class Linker { */ public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) { $ret = "<strong class=\"selflink\">{$prefix}{$html}</strong>{$trail}"; - if ( !wfRunHooks( 'SelfLinkBegin', array( $nt, &$html, &$trail, &$prefix, &$ret ) ) ) { + if ( !Hooks::run( 'SelfLinkBegin', array( $nt, &$html, &$trail, &$prefix, &$ret ) ) ) { return $ret; } @@ -495,7 +486,7 @@ class Linker { $alt = self::fnamePart( $url ); } $img = ''; - $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) ); + $success = Hooks::run( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) ); if ( !$success ) { wfDebug( "Hook LinkerMakeExternalImage changed the output of external image " . "with url {$url} and alt text {$alt} to {$img}\n", true ); @@ -549,7 +540,7 @@ class Linker { ) { $res = null; $dummy = new DummyLinker; - if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title, + if ( !Hooks::run( 'ImageBeforeProduceHTML', array( &$dummy, &$title, &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) { return $res; } @@ -931,7 +922,6 @@ class Linker { } global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl; - wfProfileIn( __METHOD__ ); if ( $label == '' ) { $label = $title->getPrefixedText(); } @@ -944,19 +934,16 @@ class Linker { $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title ); if ( $redir ) { - wfProfileOut( __METHOD__ ); return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) ); } $href = self::getUploadUrl( $title, $query ); - wfProfileOut( __METHOD__ ); return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' . htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' . $encLabel . '</a>'; } - wfProfileOut( __METHOD__ ); return self::linkKnown( $title, $encLabel, array(), wfCgiToArray( $query ) ); } @@ -1029,7 +1016,7 @@ class Linker { 'title' => $alt ); - if ( !wfRunHooks( 'LinkerMakeMediaLinkFile', + if ( !Hooks::run( 'LinkerMakeMediaLinkFile', array( $title, $file, &$html, &$attribs, &$ret ) ) ) { wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link " . "with url {$url} and text {$html} to {$ret}\n", true ); @@ -1088,7 +1075,7 @@ class Linker { } $attribs['rel'] = Parser::getExternalLinkRel( $url, $title ); $link = ''; - $success = wfRunHooks( 'LinkerMakeExternalLink', + $success = Hooks::run( 'LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) ); if ( !$success ) { wfDebug( "Hook LinkerMakeExternalLink changed the output of link " @@ -1174,10 +1161,10 @@ class Linker { $items[] = self::emailLink( $userId, $userText ); } - wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) ); + Hooks::run( 'UserToolLinksEdit', array( $userId, $userText, &$items ) ); if ( $items ) { - return wfMessage( 'word-separator' )->plain() + return wfMessage( 'word-separator' )->escaped() . '<span class="mw-usertoollinks">' . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped() . '</span>'; @@ -1264,7 +1251,6 @@ class Linker { $userId = $rev->getUser( Revision::FOR_THIS_USER ); $userText = $rev->getUserText( Revision::FOR_THIS_USER ); $link = self::userLink( $userId, $userText ) - . wfMessage( 'word-separator' )->plain() . self::userToolLinks( $userId, $userText ); } else { $link = wfMessage( 'rev-deleted-user' )->escaped(); @@ -1293,7 +1279,6 @@ class Linker { * @return mixed|string */ public static function formatComment( $comment, $title = null, $local = false ) { - wfProfileIn( __METHOD__ ); # Sanitize text a bit: $comment = str_replace( "\n", " ", $comment ); @@ -1304,12 +1289,12 @@ class Linker { $comment = self::formatAutocomments( $comment, $title, $local ); $comment = self::formatLinksInComment( $comment, $title, $local ); - wfProfileOut( __METHOD__ ); return $comment; } /** * Converts autogenerated comments in edit summaries into section links. + * * The pattern for autogen comments is / * foo * /, which makes for * some nasty regex. * We look for all comments, match any text before and after the comment, @@ -1322,16 +1307,30 @@ class Linker { * @return string Formatted comment */ private static function formatAutocomments( $comment, $title = null, $local = false ) { - return preg_replace_callback( - '!(.*)/\*\s*(.*?)\s*\*/(.*)!', - function ( $match ) use ( $title, $local ) { + // @todo $append here is something of a hack to preserve the status + // quo. Someone who knows more about bidi and such should decide + // (1) what sane rendering even *is* for an LTR edit summary on an RTL + // wiki, both when autocomments exist and when they don't, and + // (2) what markup will make that actually happen. + $append = ''; + $comment = preg_replace_callback( + // To detect the presence of content before or after the + // auto-comment, we use capturing groups inside optional zero-width + // assertions. But older versions of PCRE can't directly make + // zero-width assertions optional, so wrap them in a non-capturing + // group. + '!(?:(?<=(.)))?/\*\s*(.*?)\s*\*/(?:(?=(.)))?!', + function ( $match ) use ( $title, $local, &$append ) { global $wgLang; - $pre = $match[1]; + // Ensure all match positions are defined + $match += array( '', '', '', '' ); + + $pre = $match[1] !== ''; $auto = $match[2]; - $post = $match[3]; + $post = $match[3] !== ''; $comment = null; - wfRunHooks( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) ); + Hooks::run( 'FormatAutocomments', array( &$comment, $pre, $auto, $post, $title, $local ) ); if ( $comment === null ) { $link = ''; if ( $title ) { @@ -1359,7 +1358,7 @@ class Linker { } if ( $pre ) { # written summary $presep autocomment (summary /* section */) - $pre .= wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped(); + $pre = wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped(); } if ( $post ) { # autocomment $postsep written summary (/* section */ summary) @@ -1367,12 +1366,14 @@ class Linker { } $auto = '<span class="autocomment">' . $auto . '</span>'; $comment = $pre . $link . $wgLang->getDirMark() - . '<span dir="auto">' . $auto . $post . '</span>'; + . '<span dir="auto">' . $auto; + $append .= '</span>'; } return $comment; }, $comment ); + return $comment . $append; } /** @@ -1383,9 +1384,13 @@ class Linker { * @param string $comment Text to format links in * @param Title|null $title An optional title object used to links to sections * @param bool $local Whether section links should refer to local page + * @param string|null $wikiId Id of the wiki to link to (if not the local wiki), as used by WikiMap + * * @return string */ - public static function formatLinksInComment( $comment, $title = null, $local = false ) { + public static function formatLinksInComment( + $comment, $title = null, $local = false, $wikiId = null + ) { return preg_replace_callback( '/ \[\[ @@ -1399,7 +1404,7 @@ class Linker { \]\] ([^[]*) # 3. link trail (the text up until the next link) /x', - function ( $match ) use ( $title, $local ) { + function ( $match ) use ( $title, $local, $wikiId ) { global $wgContLang; $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|'; @@ -1455,11 +1460,22 @@ class Linker { $newTarget = clone ( $title ); $newTarget->setFragment( '#' . $target->getFragment() ); $target = $newTarget; + } - $thelink = Linker::link( - $target, - $linkText . $inside - ) . $trail; + + if ( $wikiId !== null ) { + $thelink = Linker::makeExternalLink( + WikiMap::getForeignURL( $wikiId, $target->getFullText() ), + $linkText . $inside, + /* escape = */ false // Already escaped + ) . $trail; + } else { + $thelink = Linker::link( + $target, + $linkText . $inside + ) . $trail; + } + } } if ( $thelink ) { @@ -1489,11 +1505,13 @@ class Linker { # Foobar -- normal # :Foobar -- override special treatment of prefix (images, language links) # /Foobar -- convert to CurrentPage/Foobar - # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text + # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage - # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage + # ../Foobar -- convert to CurrentPage/Foobar, + # (from CurrentPage/CurrentSubPage) + # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text + # (from CurrentPage/CurrentSubPage) - wfProfileIn( __METHOD__ ); $ret = $target; # default return value is no change # Some namespaces don't allow subpages, @@ -1537,7 +1555,7 @@ class Linker { $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) ); # / at the end means don't show full path if ( substr( $nodotdot, -1, 1 ) === '/' ) { - $nodotdot = substr( $nodotdot, 0, -1 ); + $nodotdot = rtrim( $nodotdot, '/' ); if ( $text === '' ) { $text = $nodotdot . $suffix; } @@ -1552,7 +1570,6 @@ class Linker { } } - wfProfileOut( __METHOD__ ); return $ret; } @@ -1589,7 +1606,7 @@ class Linker { * @return string HTML fragment */ public static function revComment( Revision $rev, $local = false, $isPublic = false ) { - if ( $rev->getRawComment() == "" ) { + if ( $rev->getComment( Revision::RAW ) == "" ) { return ""; } if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) { @@ -1807,7 +1824,7 @@ class Linker { $inner = self::buildRollbackLink( $rev, $context, $editCount ); if ( !in_array( 'noBrackets', $options ) ) { - $inner = $context->msg( 'brackets' )->rawParams( $inner )->plain(); + $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped(); } return '<span class="mw-rollback-link">' . $inner . '</span>'; @@ -1854,7 +1871,7 @@ class Linker { $editCount = 0; $moreRevs = false; foreach ( $res as $row ) { - if ( $rev->getRawUserText() != $row->rev_user_text ) { + if ( $rev->getUserText( Revision::RAW ) != $row->rev_user_text ) { if ( $verify && ( $row->rev_deleted & Revision::DELETED_TEXT || $row->rev_deleted & Revision::DELETED_USER @@ -1892,7 +1909,7 @@ class Linker { ) { global $wgShowRollbackEditCount, $wgMiserMode; - // To config which pages are effected by miser mode + // To config which pages are affected by miser mode $disableRollbackEditCountSpecialPage = array( 'Recentchanges', 'Watchlist' ); if ( $context === null ) { @@ -1975,7 +1992,6 @@ class Linker { $section = false, $more = null ) { global $wgLang; - wfProfileIn( __METHOD__ ); $outText = ''; if ( count( $templates ) > 0 ) { @@ -2028,14 +2044,14 @@ class Linker { if ( $titleObj->quickUserCan( 'edit' ) ) { $editLink = self::link( $titleObj, - wfMessage( 'editlink' )->text(), + wfMessage( 'editlink' )->escaped(), array(), array( 'action' => 'edit' ) ); } else { $editLink = self::link( $titleObj, - wfMessage( 'viewsourcelink' )->text(), + wfMessage( 'viewsourcelink' )->escaped(), array(), array( 'action' => 'edit' ) ); @@ -2055,7 +2071,6 @@ class Linker { $outText .= '</ul>'; } - wfProfileOut( __METHOD__ ); return $outText; } @@ -2067,7 +2082,6 @@ class Linker { * @return string HTML output */ public static function formatHiddenCategories( $hiddencats ) { - wfProfileIn( __METHOD__ ); $outText = ''; if ( count( $hiddencats ) > 0 ) { @@ -2084,7 +2098,6 @@ class Linker { } $outText .= '</ul>'; } - wfProfileOut( __METHOD__ ); return $outText; } @@ -2113,7 +2126,6 @@ class Linker { * escape), or false for no title attribute */ public static function titleAttrib( $name, $options = null ) { - wfProfileIn( __METHOD__ ); $message = wfMessage( "tooltip-$name" ); @@ -2142,7 +2154,6 @@ class Linker { } } - wfProfileOut( __METHOD__ ); return $tooltip; } @@ -2162,7 +2173,6 @@ class Linker { if ( isset( self::$accesskeycache[$name] ) ) { return self::$accesskeycache[$name]; } - wfProfileIn( __METHOD__ ); $message = wfMessage( "accesskey-$name" ); @@ -2178,7 +2188,6 @@ class Linker { } } - wfProfileOut( __METHOD__ ); self::$accesskeycache[$name] = $accesskey; return self::$accesskeycache[$name]; } @@ -2286,7 +2295,6 @@ class Linker { static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) { wfDeprecated( __METHOD__, '1.21' ); - wfProfileIn( __METHOD__ ); $query = wfCgiToArray( $query ); list( $inside, $trail ) = self::splitTrail( $trail ); if ( $text === '' ) { @@ -2295,7 +2303,6 @@ class Linker { $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail; - wfProfileOut( __METHOD__ ); return $ret; } @@ -2320,8 +2327,6 @@ class Linker { ) { wfDeprecated( __METHOD__, '1.21' ); - wfProfileIn( __METHOD__ ); - if ( $text == '' ) { $text = self::linkText( $title ); } @@ -2335,7 +2340,6 @@ class Linker { $ret = self::link( $title, "$prefix$text$inside", $attribs, $query, array( 'known', 'noclasses' ) ) . $trail; - wfProfileOut( __METHOD__ ); return $ret; } diff --git a/includes/MWNamespace.php b/includes/MWNamespace.php index 392f5582..bd685514 100644 --- a/includes/MWNamespace.php +++ b/includes/MWNamespace.php @@ -72,7 +72,7 @@ class MWNamespace { /** * @since 1.20 */ - wfRunHooks( 'NamespaceIsMovable', array( $index, &$result ) ); + Hooks::run( 'NamespaceIsMovable', array( $index, &$result ) ); return $result; } @@ -213,7 +213,7 @@ class MWNamespace { if ( is_array( $wgExtraNamespaces ) ) { $namespaces += $wgExtraNamespaces; } - wfRunHooks( 'CanonicalNamespaces', array( &$namespaces ) ); + Hooks::run( 'CanonicalNamespaces', array( &$namespaces ) ); } return $namespaces; } diff --git a/includes/MWTimestamp.php b/includes/MWTimestamp.php index 26f5e543..ea91470e 100644 --- a/includes/MWTimestamp.php +++ b/includes/MWTimestamp.php @@ -182,6 +182,11 @@ class MWTimestamp { $output .= ' GMT'; } + if ( $style == TS_MW && strlen( $output ) !== 14 ) { + throw new TimestampException( __METHOD__ . ': The timestamp cannot be represented in ' . + 'the specified format' ); + } + return $output; } @@ -221,7 +226,7 @@ class MWTimestamp { $offsetRel = $relativeTo->offsetForUser( $user ); $ts = ''; - if ( wfRunHooks( 'GetHumanTimestamp', array( &$ts, $this, $relativeTo, $user, $lang ) ) ) { + if ( Hooks::run( 'GetHumanTimestamp', array( &$ts, $this, $relativeTo, $user, $lang ) ) ) { $ts = $lang->getHumanTimestamp( $this, $relativeTo, $user ); } @@ -326,7 +331,7 @@ class MWTimestamp { $ts = ''; $diff = $this->diff( $relativeTo ); - if ( wfRunHooks( + if ( Hooks::run( 'GetRelativeTimestamp', array( &$ts, &$diff, $this, $relativeTo, $user, $lang ) ) ) { diff --git a/includes/MagicWord.php b/includes/MagicWord.php index 4d17298b..186821de 100644 --- a/includes/MagicWord.php +++ b/includes/MagicWord.php @@ -172,7 +172,6 @@ class MagicWord { 'directionmark', 'contentlanguage', 'numberofadmins', - 'numberofviews', 'cascadingsources', ); @@ -215,7 +214,6 @@ class MagicWord { 'localtimestamp' => 3600, 'pagesinnamespace' => 3600, 'numberofadmins' => 3600, - 'numberofviews' => 3600, 'numberingroup' => 3600, ); @@ -275,7 +273,7 @@ class MagicWord { static function getVariableIDs() { if ( !self::$mVariableIDsInitialised ) { # Get variable IDs - wfRunHooks( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) ); + Hooks::run( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) ); self::$mVariableIDsInitialised = true; } return self::$mVariableIDs; @@ -310,7 +308,7 @@ class MagicWord { */ static function getDoubleUnderscoreArray() { if ( is_null( self::$mDoubleUnderscoreArray ) ) { - wfRunHooks( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) ); + Hooks::run( 'GetDoubleUnderscoreIDs', array( &self::$mDoubleUnderscoreIDs ) ); self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs ); } return self::$mDoubleUnderscoreArray; @@ -332,15 +330,12 @@ class MagicWord { */ function load( $id ) { global $wgContLang; - wfProfileIn( __METHOD__ ); $this->mId = $id; $wgContLang->getMagic( $this ); if ( !$this->mSynonyms ) { $this->mSynonyms = array( 'brionmademeputthishere' ); - wfProfileOut( __METHOD__ ); throw new MWException( "Error: invalid magic word '$id'" ); } - wfProfileOut( __METHOD__ ); } /** @@ -657,7 +652,7 @@ class MagicWord { * This method uses the php feature to do several replacements at the same time, * thereby gaining some efficiency. The result is placed in the out variable * $result. The return value is true if something was replaced. - * @todo Should this be static? It doesn't seem to be used at all + * @deprecated since 1.25, unused * * @param array $magicarr * @param string $subject @@ -666,6 +661,7 @@ class MagicWord { * @return bool */ function replaceMultiple( $magicarr, $subject, &$result ) { + wfDeprecated( __METHOD__, '1.25' ); $search = array(); $replace = array(); foreach ( $magicarr as $id => $replacement ) { diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index 402494ec..ec2f40f6 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -20,10 +20,10 @@ * @file */ +use MediaWiki\Logger\LoggerFactory; + /** * The MediaWiki class is the helper class for the index.php entry point. - * - * @internal documentation reviewed 15 Mar 2010 */ class MediaWiki { /** @@ -59,7 +59,7 @@ class MediaWiki { $request = $this->context->getRequest(); $curid = $request->getInt( 'curid' ); $title = $request->getVal( 'title' ); - $action = $request->getVal( 'action', 'view' ); + $action = $request->getVal( 'action' ); if ( $request->getCheck( 'search' ) ) { // Compatibility with old search URLs which didn't use Special:Search @@ -121,7 +121,7 @@ class MediaWiki { * @return Title */ public function getTitle() { - if ( $this->context->getTitle() === null ) { + if ( !$this->context->hasTitle() ) { $this->context->setTitle( $this->parseTitle() ); } return $this->context->getTitle(); @@ -157,8 +157,6 @@ class MediaWiki { private function performRequest() { global $wgTitle; - wfProfileIn( __METHOD__ ); - $request = $this->context->getRequest(); $requestTitle = $title = $this->context->getTitle(); $output = $this->context->getOutput(); @@ -169,14 +167,13 @@ class MediaWiki { } $unused = null; // To pass it by reference - wfRunHooks( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) ); + Hooks::run( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) ); // Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty. if ( is_null( $title ) || ( $title->getDBkey() == '' && !$title->isExternal() ) || $title->isSpecial( 'Badtitle' ) ) { $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) ); - wfProfileOut( __METHOD__ ); throw new BadTitleError(); } @@ -201,12 +198,9 @@ class MediaWiki { $this->context->setTitle( $badTitle ); $wgTitle = $badTitle; - wfProfileOut( __METHOD__ ); throw new PermissionsError( 'read', $permErrors ); } - $pageView = false; // was an article or special page viewed? - // Interwiki redirects if ( $title->isExternal() ) { $rdfrom = $request->getVal( 'rdfrom' ); @@ -225,7 +219,6 @@ class MediaWiki { $output->redirect( $url, 301 ); } else { $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) ); - wfProfileOut( __METHOD__ ); throw new BadTitleError(); } // Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant @@ -233,7 +226,7 @@ class MediaWiki { && ( $request->getVal( 'title' ) === null || $title->getPrefixedDBkey() != $request->getVal( 'title' ) ) && !count( $request->getValueNames( array( 'action', 'title' ) ) ) - && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) ) + && Hooks::run( 'TestCanonicalRedirect', array( $request, $title, $output ) ) ) { if ( $title->isSpecialPage() ) { list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); @@ -270,7 +263,6 @@ class MediaWiki { } // Special pages } elseif ( NS_SPECIAL == $title->getNamespace() ) { - $pageView = true; // Actions that need to be made when we have a special pages SpecialPageFactory::executePath( $title, $this->context ); } else { @@ -278,23 +270,14 @@ class MediaWiki { // may be a redirect to another article or URL. $article = $this->initializeArticle(); if ( is_object( $article ) ) { - $pageView = true; $this->performAction( $article, $requestTitle ); } elseif ( is_string( $article ) ) { $output->redirect( $article ); } else { - wfProfileOut( __METHOD__ ); throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()" . " returned neither an object nor a URL" ); } } - - if ( $pageView ) { - // Promote user to any groups they meet the criteria for - $user->addAutopromoteOnceGroups( 'onView' ); - } - - wfProfileOut( __METHOD__ ); } /** @@ -304,7 +287,6 @@ class MediaWiki { * @return mixed An Article, or a string to redirect to another URL */ private function initializeArticle() { - wfProfileIn( __METHOD__ ); $title = $this->context->getTitle(); if ( $this->context->canUseWikiPage() ) { @@ -322,7 +304,6 @@ class MediaWiki { // NS_MEDIAWIKI has no redirects. // It is also used for CSS/JS, so performance matters here... if ( $title->getNamespace() == NS_MEDIAWIKI ) { - wfProfileOut( __METHOD__ ); return $article; } @@ -342,7 +323,7 @@ class MediaWiki { // Give extensions a change to ignore/handle redirects as needed $ignoreRedirect = $target = false; - wfRunHooks( 'InitializeArticleMaybeRedirect', + Hooks::run( 'InitializeArticleMaybeRedirect', array( &$title, &$request, &$ignoreRedirect, &$target, &$article ) ); // Follow redirects only for... redirects. @@ -353,7 +334,6 @@ class MediaWiki { if ( is_string( $target ) ) { if ( !$this->config->get( 'DisableHardRedirects' ) ) { // we'll need to redirect - wfProfileOut( __METHOD__ ); return $target; } } @@ -374,7 +354,6 @@ class MediaWiki { } } - wfProfileOut( __METHOD__ ); return $article; } @@ -385,17 +364,15 @@ class MediaWiki { * @param Title $requestTitle The original title, before any redirects were applied */ private function performAction( Page $page, Title $requestTitle ) { - wfProfileIn( __METHOD__ ); $request = $this->context->getRequest(); $output = $this->context->getOutput(); $title = $this->context->getTitle(); $user = $this->context->getUser(); - if ( !wfRunHooks( 'MediaWikiPerformAction', + if ( !Hooks::run( 'MediaWikiPerformAction', array( $output, $page, $title, $user, $request, $this ) ) ) { - wfProfileOut( __METHOD__ ); return; } @@ -406,22 +383,24 @@ class MediaWiki { if ( $action instanceof Action ) { # Let Squid cache things if we can purge them. if ( $this->config->get( 'UseSquid' ) && - in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() ) + in_array( + // Use PROTO_INTERNAL because that's what getSquidURLs() uses + wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ), + $requestTitle->getSquidURLs() + ) ) { $output->setSquidMaxage( $this->config->get( 'SquidMaxage' ) ); } $action->show(); - wfProfileOut( __METHOD__ ); return; } - if ( wfRunHooks( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) { + if ( Hooks::run( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) { $output->setStatusCode( 404 ); $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' ); } - wfProfileOut( __METHOD__ ); } /** @@ -446,7 +425,7 @@ class MediaWiki { $this->triggerJobs(); $this->restInPeace(); } catch ( Exception $e ) { - MWExceptionHandler::handle( $e ); + MWExceptionHandler::handleException( $e ); } } @@ -456,7 +435,6 @@ class MediaWiki { * @return bool */ private function checkMaxLag() { - wfProfileIn( __METHOD__ ); $maxLag = $this->context->getRequest()->getVal( 'maxlag' ); if ( !is_null( $maxLag ) ) { list( $host, $lag ) = wfGetLB()->getMaxLag(); @@ -472,33 +450,28 @@ class MediaWiki { echo "Waiting for a database server: $lag seconds lagged\n"; } - wfProfileOut( __METHOD__ ); - exit; } } - wfProfileOut( __METHOD__ ); return true; } private function main() { global $wgTitle; - wfProfileIn( __METHOD__ ); - $request = $this->context->getRequest(); // Send Ajax requests to the Ajax dispatcher. - if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action', 'view' ) == 'ajax' ) { - + if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action' ) === 'ajax' ) { // Set a dummy title, because $wgTitle == null might break things - $title = Title::makeTitle( NS_MAIN, 'AJAX' ); + $title = Title::makeTitle( NS_SPECIAL, 'Badtitle/performing an AJAX call in ' + . __METHOD__ + ); $this->context->setTitle( $title ); $wgTitle = $title; $dispatcher = new AjaxDispatcher( $this->config ); $dispatcher->performAction( $this->context->getUser() ); - wfProfileOut( __METHOD__ ); return; } @@ -508,6 +481,20 @@ class MediaWiki { $action = $this->getAction(); $wgTitle = $title; + $trxProfiler = Profiler::instance()->getTransactionProfiler(); + $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) ); + + // Aside from rollback, master queries should not happen on GET requests. + // Periodic or "in passing" updates on GET should use the job queue. + if ( !$request->wasPosted() + && in_array( $action, array( 'view', 'edit', 'history' ) ) + ) { + $trxProfiler->setExpectation( 'masterConns', 0, __METHOD__ ); + $trxProfiler->setExpectation( 'writes', 0, __METHOD__ ); + } else { + $trxProfiler->setExpectation( 'maxAffected', 500, __METHOD__ ); + } + // If the user has forceHTTPS set to true, or if the user // is in a group requiring HTTPS, or if they have the HTTPS // preference set, redirect them to HTTPS. @@ -530,7 +517,7 @@ class MediaWiki { $redirUrl = preg_replace( '#^http://#', 'https://', $oldUrl ); // ATTENTION: This hook is likely to be removed soon due to overall design of the system. - if ( wfRunHooks( 'BeforeHttpsRedirect', array( $this->context, &$redirUrl ) ) ) { + if ( Hooks::run( 'BeforeHttpsRedirect', array( $this->context, &$redirUrl ) ) ) { if ( $request->wasPosted() ) { // This is weird and we'd hope it almost never happens. This @@ -544,20 +531,18 @@ class MediaWiki { wfDebugLog( 'RedirectedPosts', "Redirected from HTTP to HTTPS: $oldUrl" ); } // Setup dummy Title, otherwise OutputPage::redirect will fail - $title = Title::newFromText( NS_MAIN, 'REDIR' ); + $title = Title::newFromText( 'REDIR', NS_MAIN ); $this->context->setTitle( $title ); $output = $this->context->getOutput(); // Since we only do this redir to change proto, always send a vary header $output->addVaryHeader( 'X-Forwarded-Proto' ); $output->redirect( $redirUrl ); $output->output(); - wfProfileOut( __METHOD__ ); return; } } if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) { - wfProfileIn( 'main-try-filecache' ); if ( HTMLFileCache::useFileCache( $this->context ) ) { // Try low-level file cache hit $cache = new HTMLFileCache( $title, $action ); @@ -572,12 +557,9 @@ class MediaWiki { $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() ); // Tell OutputPage that output is taken care of $this->context->getOutput()->disable(); - wfProfileOut( 'main-try-filecache' ); - wfProfileOut( __METHOD__ ); return; } } - wfProfileOut( 'main-try-filecache' ); } // Actually do the work of the request and build up any output @@ -593,13 +575,16 @@ class MediaWiki { // Output everything! $this->context->getOutput()->output(); - wfProfileOut( __METHOD__ ); } /** * Ends this task peacefully */ public function restInPeace() { + // Ignore things like master queries/connections on GET requests + // as long as they are in deferred updates (which catch errors). + Profiler::instance()->getTransactionProfiler()->resetExpectations(); + // Do any deferred jobs DeferredUpdates::doUpdates( 'commit' ); @@ -627,8 +612,6 @@ class MediaWiki { return; // recursion guard } - $section = new ProfileSection( __METHOD__ ); - if ( $jobRunRate < 1 ) { $max = mt_getrandmax(); if ( mt_rand( 0, $max ) > $max * $jobRunRate ) { @@ -639,9 +622,11 @@ class MediaWiki { $n = intval( $jobRunRate ); } + $runJobsLogger = LoggerFactory::getInstance( 'runJobs' ); + if ( !$this->config->get( 'RunJobsAsync' ) ) { // Fall back to running the job here while the user waits - $runner = new JobRunner(); + $runner = new JobRunner( $runJobsLogger ); $runner->run( array( 'maxJobs' => $n ) ); return; } @@ -674,29 +659,34 @@ class MediaWiki { ); wfRestoreWarnings(); if ( !$sock ) { - wfDebugLog( 'runJobs', "Failed to start cron API (socket error $errno): $errstr\n" ); + $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" ); // Fall back to running the job here while the user waits - $runner = new JobRunner(); + $runner = new JobRunner( $runJobsLogger ); $runner->run( array( 'maxJobs' => $n ) ); return; } $url = wfAppendQuery( wfScript( 'index' ), $query ); - $req = "POST $url HTTP/1.1\r\nHost: {$info['host']}\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n"; + $req = ( + "POST $url HTTP/1.1\r\n" . + "Host: {$info['host']}\r\n" . + "Connection: Close\r\n" . + "Content-Length: 0\r\n\r\n" + ); - wfDebugLog( 'runJobs', "Running $n job(s) via '$url'\n" ); + $runJobsLogger->info( "Running $n job(s) via '$url'" ); // Send a cron API request to be performed in the background. // Give up if this takes too long to send (which should be rare). stream_set_timeout( $sock, 1 ); $bytes = fwrite( $sock, $req ); if ( $bytes !== strlen( $req ) ) { - wfDebugLog( 'runJobs', "Failed to start cron API (socket write error)\n" ); + $runJobsLogger->error( "Failed to start cron API (socket write error)" ); } else { // Do not wait for the response (the script should handle client aborts). // Make sure that we don't close before that script reaches ignore_user_abort(). $status = fgets( $sock ); if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) { - wfDebugLog( 'runJobs', "Failed to start cron API: received '$status'\n" ); + $runJobsLogger->error( "Failed to start cron API: received '$status'" ); } } fclose( $sock ); diff --git a/includes/MediaWikiVersionFetcher.php b/includes/MediaWikiVersionFetcher.php index 439e53f4..943bc9fc 100644 --- a/includes/MediaWikiVersionFetcher.php +++ b/includes/MediaWikiVersionFetcher.php @@ -4,7 +4,6 @@ * Provides access to MediaWiki's version without requiring MediaWiki (or anything else) * being loaded first. * - * @licence GNU GPL v2+ * @author Jeroen De Dauw < jeroendedauw@gmail.com > */ class MediaWikiVersionFetcher { @@ -19,7 +18,7 @@ class MediaWikiVersionFetcher { $defaultSettings = file_get_contents( __DIR__ . '/DefaultSettings.php' ); $matches = array(); - preg_match( "/wgVersion = '([-0-9a-zA-Z\.]+)';/", $defaultSettings, $matches ); + preg_match( "/wgVersion = '([0-9a-zA-Z\.\-]+)';/", $defaultSettings, $matches ); if ( count( $matches ) !== 2 ) { throw new RuntimeException( 'Could not extract the MediaWiki version from DefaultSettings.php' ); diff --git a/includes/Message.php b/includes/Message.php index 4df0d809..134af0ed 100644 --- a/includes/Message.php +++ b/includes/Message.php @@ -156,7 +156,7 @@ * * @since 1.17 */ -class Message { +class Message implements MessageSpecifier { /** * In which language to get this message. True, which is the default, @@ -249,7 +249,7 @@ class Message { $this->key = reset( $this->keysToTry ); $this->parameters = array_values( $params ); - $this->language = $language ? $language : $wgLang; + $this->language = $language ?: $wgLang; } /** @@ -276,7 +276,7 @@ class Message { * Returns the message key. * * If a list of multiple possible keys was supplied to the constructor, this method may - * return any of these keys. After the message ahs been fetched, this method will return + * return any of these keys. After the message has been fetched, this method will return * the key that was actually used to fetch the message. * * @since 1.21 @@ -541,6 +541,30 @@ class Message { } /** + * Add parameters that are plaintext and will be passed through without + * the content being evaluated. Plaintext parameters are not valid as + * arguments to parser functions. This differs from self::rawParams in + * that the Message class handles escaping to match the output format. + * + * @since 1.25 + * + * @param string|string[] $param,... plaintext parameters, or a single argument that is + * an array of plaintext parameters. + * + * @return Message $this + */ + public function plaintextParams( /*...*/ ) { + $params = func_get_args(); + if ( isset( $params[0] ) && is_array( $params[0] ) ) { + $params = $params[0]; + } + foreach ( $params as $param ) { + $this->parameters[] = self::plaintextParam( $param ); + } + return $this; + } + + /** * Set the language and the title from a context object * * @since 1.19 @@ -674,11 +698,10 @@ class Message { $string = $this->fetchMessage(); if ( $string === false ) { - $key = htmlspecialchars( $this->key ); - if ( $this->format === 'plain' ) { - return '<' . $key . '>'; + if ( $this->format === 'plain' || $this->format === 'text' ) { + return '<' . $this->key . '>'; } - return '<' . $key . '>'; + return '<' . htmlspecialchars( $this->key ) . '>'; } # Replace $* with a list of parameters for &uselang=qqx. @@ -735,10 +758,10 @@ class Message { // Doh! Cause a fatal error after all? } - if ( $this->format === 'plain' ) { + if ( $this->format === 'plain' || $this->format === 'text' ) { return '<' . $this->key . '>'; } - return '<' . $this->key . '>'; + return '<' . htmlspecialchars( $this->key ) . '>'; } } @@ -917,6 +940,17 @@ class Message { } /** + * @since 1.25 + * + * @param string $plaintext + * + * @return string[] Array with a single "plaintext" key. + */ + public static function plaintextParam( $plaintext ) { + return array( 'plaintext' => $plaintext ); + } + + /** * Substitutes any parameters into the message text. * * @since 1.17 @@ -965,6 +999,8 @@ class Message { return array( 'before', $this->language->formatSize( $param['size'] ) ); } elseif ( isset( $param['bitrate'] ) ) { return array( 'before', $this->language->formatBitrate( $param['bitrate'] ) ); + } elseif ( isset( $param['plaintext'] ) ) { + return array( 'after', $this->formatPlaintext( $param['plaintext'] ) ); } else { $warning = 'Invalid parameter for message "' . $this->getKey() . '": ' . htmlspecialchars( serialize( $param ) ); @@ -1050,6 +1086,31 @@ class Message { return $this->message; } + /** + * Formats a message parameter wrapped with 'plaintext'. Ensures that + * the entire string is displayed unchanged when displayed in the output + * format. + * + * @since 1.25 + * + * @param string $plaintext String to ensure plaintext output of + * + * @return string Input plaintext encoded for output to $this->format + */ + protected function formatPlaintext( $plaintext ) { + switch ( $this->format ) { + case 'text': + case 'plain': + return $plaintext; + + case 'parse': + case 'block-parse': + case 'escaped': + default: + return htmlspecialchars( $plaintext, ENT_QUOTES ); + + } + } } /** diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php index e3b4dbe8..011cae66 100644 --- a/includes/MessageBlobStore.php +++ b/includes/MessageBlobStore.php @@ -36,15 +36,12 @@ class MessageBlobStore { * Get the singleton instance * * @since 1.24 + * @deprecated since 1.25 * @return MessageBlobStore */ public static function getInstance() { - static $instance = null; - if ( $instance === null ) { - $instance = new self; - } - - return $instance; + wfDeprecated( __METHOD__, '1.25' ); + return new self; } /** @@ -56,9 +53,7 @@ class MessageBlobStore { * @return array An array mapping module names to message blobs */ public function get( ResourceLoader $resourceLoader, $modules, $lang ) { - wfProfileIn( __METHOD__ ); if ( !count( $modules ) ) { - wfProfileOut( __METHOD__ ); return array(); } // Try getting from the DB first @@ -73,7 +68,6 @@ class MessageBlobStore { } } - wfProfileOut( __METHOD__ ); return $blobs; } @@ -130,7 +124,7 @@ class MessageBlobStore { ); } } - } catch ( Exception $e ) { + } catch ( DBError $e ) { wfDebug( __METHOD__ . " failed to update DB: $e\n" ); } return $blob; diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index bfd60111..ebe98a3c 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -200,7 +200,7 @@ class MimeMagic { global $IP; # Allow media handling extensions adding MIME-types and MIME-info - wfRunHooks( 'MimeMagicInit', array( $this ) ); + Hooks::run( 'MimeMagicInit', array( $this ) ); $types = MM_WELL_KNOWN_MIME_TYPES; @@ -210,7 +210,7 @@ class MimeMagic { } if ( $mimeTypeFile ) { - if ( is_file( $mimeTypeFile ) and is_readable( $mimeTypeFile ) ) { + if ( is_file( $mimeTypeFile ) && is_readable( $mimeTypeFile ) ) { wfDebug( __METHOD__ . ": loading mime types from $mimeTypeFile\n" ); $types .= "\n"; $types .= file_get_contents( $mimeTypeFile ); @@ -218,7 +218,7 @@ class MimeMagic { wfDebug( __METHOD__ . ": can't load mime types from $mimeTypeFile\n" ); } } else { - wfDebug( __METHOD__ . ": no mime types file defined, using build-ins only.\n" ); + wfDebug( __METHOD__ . ": no mime types file defined, using built-ins only.\n" ); } $types .= "\n" . $this->mExtraTypes; @@ -287,7 +287,7 @@ class MimeMagic { $info = MM_WELL_KNOWN_MIME_INFO; if ( $mimeInfoFile ) { - if ( is_file( $mimeInfoFile ) and is_readable( $mimeInfoFile ) ) { + if ( is_file( $mimeInfoFile ) && is_readable( $mimeInfoFile ) ) { wfDebug( __METHOD__ . ": loading mime info from $mimeInfoFile\n" ); $info .= "\n"; $info .= file_get_contents( $mimeInfoFile ); @@ -295,7 +295,7 @@ class MimeMagic { wfDebug( __METHOD__ . ": can't load mime info from $mimeInfoFile\n" ); } } else { - wfDebug( __METHOD__ . ": no mime info file defined, using build-ins only.\n" ); + wfDebug( __METHOD__ . ": no mime info file defined, using built-ins only.\n" ); } $info .= "\n" . $this->mExtraInfo; @@ -569,7 +569,7 @@ class MimeMagic { } # Media handling extensions can improve the MIME detected - wfRunHooks( 'MimeMagicImproveFromExtension', array( $this, $ext, &$mime ) ); + Hooks::run( 'MimeMagicImproveFromExtension', array( $this, $ext, &$mime ) ); if ( isset( $this->mMimeTypeAliases[$mime] ) ) { $mime = $this->mMimeTypeAliases[$mime]; @@ -802,7 +802,7 @@ class MimeMagic { # people will hopefully nag and submit patches :) $mime = false; # Some strings by reference for performance - assuming well-behaved hooks - wfRunHooks( + Hooks::run( 'MimeMagicGuessFromContent', array( $this, &$head, &$tail, $file, &$mime ) ); diff --git a/includes/MovePage.php b/includes/MovePage.php index fdece8d5..de7da3f9 100644 --- a/includes/MovePage.php +++ b/includes/MovePage.php @@ -42,6 +42,188 @@ class MovePage { $this->newTitle = $newTitle; } + public function checkPermissions( User $user, $reason ) { + $status = new Status(); + + $errors = wfMergeErrorArrays( + $this->oldTitle->getUserPermissionsErrors( 'move', $user ), + $this->oldTitle->getUserPermissionsErrors( 'edit', $user ), + $this->newTitle->getUserPermissionsErrors( 'move-target', $user ), + $this->newTitle->getUserPermissionsErrors( 'edit', $user ) + ); + + // Convert into a Status object + if ( $errors ) { + foreach ( $errors as $error ) { + call_user_func_array( array( $status, 'fatal' ), $error ); + } + } + + if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) { + // This is kind of lame, won't display nice + $status->fatal( 'spamprotectiontext' ); + } + + # The move is allowed only if (1) the target doesn't exist, or + # (2) the target is a redirect to the source, and has no history + # (so we can undo bad moves right after they're done). + + if ( $this->newTitle->getArticleID() ) { # Target exists; check for validity + if ( !$this->isValidMoveTarget() ) { + $status->fatal( 'articleexists' ); + } + } else { + $tp = $this->newTitle->getTitleProtection(); + if ( $tp !== false ) { + if ( !$user->isAllowed( $tp['permission'] ) ) { + $status->fatal( 'cantmove-titleprotected' ); + } + } + } + + Hooks::run( 'MovePageCheckPermissions', + array( $this->oldTitle, $this->newTitle, $user, $reason, $status ) + ); + + return $status; + } + + /** + * Does various sanity checks that the move is + * valid. Only things based on the two titles + * should be checked here. + * + * @return Status + */ + public function isValidMove() { + global $wgContentHandlerUseDB; + $status = new Status(); + + if ( $this->oldTitle->equals( $this->newTitle ) ) { + $status->fatal( 'selfmove' ); + } + if ( !$this->oldTitle->isMovable() ) { + $status->fatal( 'immobile-source-namespace', $this->oldTitle->getNsText() ); + } + if ( $this->newTitle->isExternal() ) { + $status->fatal( 'immobile-target-namespace-iw' ); + } + if ( !$this->newTitle->isMovable() ) { + $status->fatal( 'immobile-target-namespace', $this->newTitle->getNsText() ); + } + + $oldid = $this->oldTitle->getArticleID(); + + if ( strlen( $this->newTitle->getDBkey() ) < 1 ) { + $status->fatal( 'articleexists' ); + } + if ( + ( $this->oldTitle->getDBkey() == '' ) || + ( !$oldid ) || + ( $this->newTitle->getDBkey() == '' ) + ) { + $status->fatal( 'badarticleerror' ); + } + + // Content model checks + if ( !$wgContentHandlerUseDB && + $this->oldTitle->getContentModel() !== $this->newTitle->getContentModel() ) { + // can't move a page if that would change the page's content model + $status->fatal( + 'bad-target-model', + ContentHandler::getLocalizedName( $this->oldTitle->getContentModel() ), + ContentHandler::getLocalizedName( $this->newTitle->getContentModel() ) + ); + } + + // Image-specific checks + if ( $this->oldTitle->inNamespace( NS_FILE ) ) { + $status->merge( $this->isValidFileMove() ); + } + + if ( $this->newTitle->inNamespace( NS_FILE ) && !$this->oldTitle->inNamespace( NS_FILE ) ) { + $status->fatal( 'nonfile-cannot-move-to-file' ); + } + + // Hook for extensions to say a title can't be moved for technical reasons + Hooks::run( 'MovePageIsValidMove', array( $this->oldTitle, $this->newTitle, $status ) ); + + return $status; + } + + /** + * Sanity checks for when a file is being moved + * + * @return Status + */ + protected function isValidFileMove() { + $status = new Status(); + $file = wfLocalFile( $this->oldTitle ); + $file->load( File::READ_LATEST ); + if ( $file->exists() ) { + if ( $this->newTitle->getText() != wfStripIllegalFilenameChars( $this->newTitle->getText() ) ) { + $status->fatal( 'imageinvalidfilename' ); + } + if ( !File::checkExtensionCompatibility( $file, $this->newTitle->getDBkey() ) ) { + $status->fatal( 'imagetypemismatch' ); + } + } + + if ( !$this->newTitle->inNamespace( NS_FILE ) ) { + $status->fatal( 'imagenocrossnamespace' ); + } + + return $status; + } + + /** + * Checks if $this can be moved to a given Title + * - Selects for update, so don't call it unless you mean business + * + * @since 1.25 + * @return bool + */ + protected function isValidMoveTarget() { + # Is it an existing file? + if ( $this->newTitle->inNamespace( NS_FILE ) ) { + $file = wfLocalFile( $this->newTitle ); + $file->load( File::READ_LATEST ); + if ( $file->exists() ) { + wfDebug( __METHOD__ . ": file exists\n" ); + return false; + } + } + # Is it a redirect with no history? + if ( !$this->newTitle->isSingleRevRedirect() ) { + wfDebug( __METHOD__ . ": not a one-rev redirect\n" ); + return false; + } + # Get the article text + $rev = Revision::newFromTitle( $this->newTitle, false, Revision::READ_LATEST ); + if ( !is_object( $rev ) ) { + return false; + } + $content = $rev->getContent(); + # Does the redirect point to the source? + # Or is it a broken self-redirect, usually caused by namespace collisions? + $redirTitle = $content ? $content->getRedirectTarget() : null; + + if ( $redirTitle ) { + if ( $redirTitle->getPrefixedDBkey() !== $this->oldTitle->getPrefixedDBkey() && + $redirTitle->getPrefixedDBkey() !== $this->newTitle->getPrefixedDBkey() ) { + wfDebug( __METHOD__ . ": redirect points to other page\n" ); + return false; + } else { + return true; + } + } else { + # Fail safe (not a redirect after all. strange.) + wfDebug( __METHOD__ . ": failsafe: database says " . $this->newTitle->getPrefixedDBkey() . + " is a redirect, but it doesn't contain a valid redirect.\n" ); + return false; + } + } + /** * @param User $user * @param string $reason @@ -51,11 +233,14 @@ class MovePage { public function move( User $user, $reason, $createRedirect ) { global $wgCategoryCollation; + Hooks::run( 'TitleMove', array( $this->oldTitle, $this->newTitle, $user ) ); + // If it is a file, move it first. // It is done before all other moving stuff is done because it's hard to revert. $dbw = wfGetDB( DB_MASTER ); if ( $this->oldTitle->getNamespace() == NS_FILE ) { $file = wfLocalFile( $this->oldTitle ); + $file->load( File::READ_LATEST ); if ( $file->exists() ) { $status = $file->move( $this->newTitle ); if ( !$status->isOk() ) { @@ -188,9 +373,11 @@ class MovePage { $dbw->commit( __METHOD__ ); - wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) ); + Hooks::run( + 'TitleMoveComplete', + array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) + ); return Status::newGood(); - } /** @@ -258,6 +445,9 @@ class MovePage { $dbw = wfGetDB( DB_MASTER ); + $oldpage = WikiPage::factory( $this->oldTitle ); + $oldcountable = $oldpage->isCountable(); + $newpage = WikiPage::factory( $nt ); if ( $moveOverRedirect ) { @@ -302,10 +492,11 @@ class MovePage { $newpage->updateRevisionOn( $dbw, $nullRevision ); - wfRunHooks( 'NewRevisionFromEditComplete', + Hooks::run( 'NewRevisionFromEditComplete', array( $newpage, $nullRevision, $nullRevision->getParentId(), $user ) ); - $newpage->doEditUpdates( $nullRevision, $user, array( 'changed' => false ) ); + $newpage->doEditUpdates( $nullRevision, $user, + array( 'changed' => false, 'moved' => true, 'oldcountable' => $oldcountable ) ); if ( !$moveOverRedirect ) { WikiPage::onArticleCreate( $nt ); @@ -328,7 +519,7 @@ class MovePage { $redirectRevision->insertOn( $dbw ); $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 ); - wfRunHooks( 'NewRevisionFromEditComplete', + Hooks::run( 'NewRevisionFromEditComplete', array( $redirectArticle, $redirectRevision, false, $user ) ); $redirectArticle->doEditUpdates( $redirectRevision, $user, array( 'created' => true ) ); @@ -339,5 +530,4 @@ class MovePage { $logid = $logEntry->insert(); $logEntry->publish( $logid ); } - -}
\ No newline at end of file +} diff --git a/includes/templates/NoLocalSettings.php b/includes/NoLocalSettings.php index 5b88dfd1..6de9bfcd 100644 --- a/includes/templates/NoLocalSettings.php +++ b/includes/NoLocalSettings.php @@ -1,7 +1,6 @@ <?php -// @codingStandardsIgnoreFile /** - * Template used when there is no LocalSettings.php file. + * Display an error page when there is no LocalSettings.php file. * * 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 @@ -19,17 +18,8 @@ * http://www.gnu.org/copyleft/gpl.html * * @file - * @ingroup Templates */ -if ( !defined( 'MEDIAWIKI' ) ) { - die( "NoLocalSettings.php is not a valid MediaWiki entry point\n" ); -} - -if ( !isset( $wgVersion ) ) { - $wgVersion = 'VERSION'; -} - # bug 30219 : can not use pathinfo() on URLs since slashes do not match $matches = array(); $ext = 'php'; @@ -39,6 +29,7 @@ foreach ( array_filter( explode( '/', $_SERVER['PHP_SELF'] ) ) as $part ) { $path .= "$part/"; } else { $ext = $matches[1] == 'php5' ? 'php5' : 'php'; + break; } } @@ -52,46 +43,21 @@ if ( !function_exists( 'session_name' ) ) { error_reporting( $oldReporting ); $installerStarted = ( $success && isset( $_SESSION['installData'] ) ); } -?> -<!DOCTYPE html> -<html lang="en" dir="ltr"> - <head> - <meta charset="UTF-8" /> - <title>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></title> - <style media='screen'> - html, body { - color: #000; - background-color: #fff; - font-family: sans-serif; - text-align: center; - } - - h1 { - font-size: 150%; - } - </style> - </head> - <body> - <img src="<?php echo htmlspecialchars( $path ) ?>resources/assets/mediawiki.png" alt='The MediaWiki logo' /> - <h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></h1> - <div class='error'> - <?php if ( !file_exists( MW_CONFIG_FILE ) ) { ?> - <p>LocalSettings.php not found.</p> - <p> - <?php - if ( $installerStarted ) { - echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\">complete the installation</a> and download LocalSettings.php."; - } else { - echo "Please <a href=\"" . htmlspecialchars( $path ) . "mw-config/index." . htmlspecialchars( $ext ) . "\">set up the wiki</a> first."; - } - ?> - </p> - <?php } else { ?> - <p>LocalSettings.php not readable.</p> - <p>Please correct file permissions and try again.</p> - <?php } ?> +$templateParser = new TemplateParser(); - </div> - </body> -</html> +# Render error page if no LocalSettings file can be found +try { + echo $templateParser->processTemplate( + 'NoLocalSettings', + array( + 'wgVersion' => ( isset( $wgVersion ) ? $wgVersion : 'VERSION' ), + 'path' => $path, + 'ext' => $ext, + 'localSettingsExists' => file_exists( MW_CONFIG_FILE ), + 'installerStarted' => $installerStarted + ) + ); +} catch ( Exception $e ) { + echo 'Error: ' . htmlspecialchars( $e->getMessage() ); +} diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php index b0bbcddb..c6209eeb 100644 --- a/includes/OutputHandler.php +++ b/includes/OutputHandler.php @@ -129,7 +129,8 @@ function wfGzipHandler( $s ) { $headers = headers_list(); $foundVary = false; foreach ( $headers as $header ) { - if ( substr( $header, 0, 5 ) == 'Vary:' ) { + $headerName = strtolower( substr( $header, 0, 5 ) ); + if ( $headerName == 'vary:' ) { $foundVary = true; break; } diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 55b1da00..7e671878 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -122,6 +122,9 @@ class OutputPage extends ContextSource { /** @var array */ protected $mCategories = array(); + /** @var array */ + protected $mIndicators = array(); + /** @var array Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page') */ private $mLanguageLinks = array(); @@ -193,12 +196,6 @@ class OutputPage extends ContextSource { // Parser related. - /** - * @var int - * @todo Unused? - */ - private $mContainsOldMagic = 0; - /** @var int */ protected $mContainsNewMagic = 0; @@ -244,8 +241,9 @@ class OutputPage extends ContextSource { protected $mSquidMaxage = 0; /** - * @var bool - * @todo Document + * @var bool Controls if anti-clickjacking / frame-breaking headers will + * be sent. This should be done for pages where edit actions are possible. + * Setters: $this->preventClickjacking() and $this->allowClickjacking(). */ protected $mPreventClickjacking = true; @@ -783,7 +781,7 @@ class OutputPage extends ContextSource { // bug 44570: the core page itself may not change, but resources might $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) ); } - wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); + Hooks::run( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); $maxModified = max( $modifiedTimes ); $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); @@ -1030,17 +1028,29 @@ class OutputPage extends ContextSource { } /** - * Add a subtitle containing a backlink to a page + * Build message object for a subtitle containing a backlink to a page * * @param Title $title Title to link to * @param array $query Array of additional parameters to include in the link + * @return Message + * @since 1.25 */ - public function addBacklinkSubtitle( Title $title, $query = array() ) { + public static function buildBacklinkSubtitle( Title $title, $query = array() ) { if ( $title->isRedirect() ) { $query['redirect'] = 'no'; } - $this->addSubtitle( $this->msg( 'backlinksubtitle' ) - ->rawParams( Linker::link( $title, null, array(), $query ) ) ); + return wfMessage( 'backlinksubtitle' ) + ->rawParams( Linker::link( $title, null, array(), $query ) ); + } + + /** + * Add a subtitle containing a backlink to a page + * + * @param Title $title Title to link to + * @param array $query Array of additional parameters to include in the link + */ + public function addBacklinkSubtitle( Title $title, $query = array() ) { + $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) ); } /** @@ -1060,7 +1070,7 @@ class OutputPage extends ContextSource { } /** - * Set the page as printable, i.e. it'll be displayed with with all + * Set the page as printable, i.e. it'll be displayed with all * print styles included */ public function setPrintable() { @@ -1310,7 +1320,7 @@ class OutputPage extends ContextSource { } # Add the remaining categories to the skin - if ( wfRunHooks( + if ( Hooks::run( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { @@ -1363,6 +1373,65 @@ class OutputPage extends ContextSource { } /** + * Add an array of indicators, with their identifiers as array + * keys and HTML contents as values. + * + * In case of duplicate keys, existing values are overwritten. + * + * @param array $indicators + * @since 1.25 + */ + public function setIndicators( array $indicators ) { + $this->mIndicators = $indicators + $this->mIndicators; + // Keep ordered by key + ksort( $this->mIndicators ); + } + + /** + * Get the indicators associated with this page. + * + * The array will be internally ordered by item keys. + * + * @return array Keys: identifiers, values: HTML contents + * @since 1.25 + */ + public function getIndicators() { + return $this->mIndicators; + } + + /** + * Adds help link with an icon via page indicators. + * Link target can be overridden by a local message containing a wikilink: + * the message key is: lowercase action or special page name + '-helppage'. + * @param string $to Target MediaWiki.org page title or encoded URL. + * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o. + * @since 1.25 + */ + public function addHelpLink( $to, $overrideBaseUrl = false ) { + $this->addModuleStyles( 'mediawiki.helplink' ); + $text = $this->msg( 'helppage-top-gethelp' )->escaped(); + + if ( $overrideBaseUrl ) { + $helpUrl = $to; + } else { + $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) ); + $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded"; + } + + $link = Html::rawElement( + 'a', + array( + 'href' => $helpUrl, + 'target' => '_blank', + 'class' => 'mw-helplink', + ), + $text + ); + + $this->setIndicators( array( 'mw-helplink' => $link ) ); + } + + /** * Do not allow scripts which can be modified by wiki users to load on this page; * only allow scripts bundled with, or generated by, the software. * Site-wide styles are controlled by a config setting, since they can be @@ -1585,6 +1654,7 @@ class OutputPage extends ContextSource { * @param string $text * @param bool $linestart Is this the start of a line? * @param bool $interface Is this text in the user interface language? + * @throws MWException */ public function addWikiText( $text, $linestart = true, $interface = true ) { $title = $this->getTitle(); // Work around E_STRICT @@ -1642,8 +1712,6 @@ class OutputPage extends ContextSource { ) { global $wgParser; - wfProfileIn( __METHOD__ ); - $popts = $this->parserOptions(); $oldTidy = $popts->setTidy( $tidy ); $popts->setInterfaceMessage( (bool)$interface ); @@ -1657,7 +1725,6 @@ class OutputPage extends ContextSource { $this->addParserOutput( $parserOutput ); - wfProfileOut( __METHOD__ ); } /** @@ -1681,6 +1748,7 @@ class OutputPage extends ContextSource { public function addParserOutputMetadata( $parserOutput ) { $this->mLanguageLinks += $parserOutput->getLanguageLinks(); $this->addCategoryLinks( $parserOutput->getCategories() ); + $this->setIndicators( $parserOutput->getIndicators() ); $this->mNewSectionLink = $parserOutput->getNewSection(); $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); @@ -1723,8 +1791,8 @@ class OutputPage extends ContextSource { // Link flags are ignored for now, but may in the future be // used to mark individual language links. $linkFlags = array(); - wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) ); - wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); + Hooks::run( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) ); + Hooks::run( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); } /** @@ -1753,7 +1821,7 @@ class OutputPage extends ContextSource { */ public function addParserOutputText( $parserOutput ) { $text = $parserOutput->getText(); - wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); + Hooks::run( 'OutputPageBeforeHTML', array( &$this, &$text ) ); $this->addHTML( $text ); } @@ -1878,7 +1946,7 @@ class OutputPage extends ContextSource { ), $config->get( 'CacheVaryCookies' ) ); - wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) ); + Hooks::run( 'GetCacheVaryCookies', array( $this, &$cookies ) ); } return $cookies; } @@ -2125,14 +2193,10 @@ class OutputPage extends ContextSource { * the object, let's actually output it: */ public function output() { - global $wgLanguageCode; - if ( $this->mDoNothing ) { return; } - wfProfileIn( __METHOD__ ); - $response = $this->getRequest()->response(); $config = $this->getConfig(); @@ -2143,7 +2207,7 @@ class OutputPage extends ContextSource { $redirect = $this->mRedirect; $code = $this->mRedirectCode; - if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { + if ( Hooks::run( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { if ( $code == '301' || $code == '303' ) { if ( !$config->get( 'DebugRedirects' ) ) { $message = HttpStatus::getMessage( $code ); @@ -2167,7 +2231,6 @@ class OutputPage extends ContextSource { } } - wfProfileOut( __METHOD__ ); return; } elseif ( $this->mStatusCode ) { $message = HttpStatus::getMessage( $this->mStatusCode ); @@ -2180,7 +2243,7 @@ class OutputPage extends ContextSource { ob_start(); $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' ); - $response->header( 'Content-language: ' . $wgLanguageCode ); + $response->header( 'Content-language: ' . $config->get( 'LanguageCode' ) ); // Avoid Internet Explorer "compatibility view" in IE 8-10, so that // jQuery etc. can work correctly. @@ -2220,21 +2283,18 @@ class OutputPage extends ContextSource { // Hook that allows last minute changes to the output page, e.g. // adding of CSS or Javascript by extensions. - wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) ); + Hooks::run( 'BeforePageDisplay', array( &$this, &$sk ) ); - wfProfileIn( 'Output-skin' ); $sk->outputPage(); - wfProfileOut( 'Output-skin' ); } // This hook allows last minute changes to final overall output by modifying output buffer - wfRunHooks( 'AfterFinalPageOutput', array( $this ) ); + Hooks::run( 'AfterFinalPageOutput', array( $this ) ); $this->sendCacheControl(); ob_end_flush(); - wfProfileOut( __METHOD__ ); } /** @@ -2454,90 +2514,32 @@ class OutputPage extends ContextSource { } /** - * Display a page stating that the Wiki is in read-only mode, - * and optionally show the source of the page that the user - * was trying to edit. Should only be called (for this - * purpose) after wfReadOnly() has returned true. + * Display a page stating that the Wiki is in read-only mode. + * Should only be called after wfReadOnly() has returned true. * - * For historical reasons, this function is _also_ used to - * show the error message when a user tries to edit a page - * they are not allowed to edit. (Unless it's because they're - * blocked, then we show blockedPage() instead.) In this - * case, the second parameter should be set to true and a list - * of reasons supplied as the third parameter. + * Historically, this function was used to show the source of the page that the user + * was trying to edit and _also_ permissions error messages. The relevant code was + * moved into EditPage in 1.19 (r102024 / d83c2a431c2a) and removed here in 1.25. * - * @todo Needs to be split into multiple functions. - * - * @param string $source Source code to show (or null). - * @param bool $protected Is this a permissions error? - * @param array $reasons List of reasons for this error, as returned by - * Title::getUserPermissionsErrors(). - * @param string $action Action that was denied or null if unknown + * @deprecated since 1.25; throw the exception directly * @throws ReadOnlyError */ - public function readOnlyPage( $source = null, $protected = false, - array $reasons = array(), $action = null - ) { - $this->setRobotPolicy( 'noindex,nofollow' ); - $this->setArticleRelated( false ); - - // If no reason is given, just supply a default "I can't let you do - // that, Dave" message. Should only occur if called by legacy code. - if ( $protected && empty( $reasons ) ) { - $reasons[] = array( 'badaccess-group0' ); - } - - if ( !empty( $reasons ) ) { - // Permissions error - if ( $source ) { - $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); - $this->addBacklinkSubtitle( $this->getTitle() ); - } else { - $this->setPageTitle( $this->msg( 'badaccess' ) ); - } - $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); - } else { - // Wiki is read only - throw new ReadOnlyError; - } - - // Show source, if supplied - if ( is_string( $source ) ) { - $this->addWikiMsg( 'viewsourcetext' ); - - $pageLang = $this->getTitle()->getPageLanguage(); - $params = array( - 'id' => 'wpTextbox1', - 'name' => 'wpTextbox1', - 'cols' => $this->getUser()->getOption( 'cols' ), - 'rows' => $this->getUser()->getOption( 'rows' ), - 'readonly' => 'readonly', - 'lang' => $pageLang->getHtmlCode(), - 'dir' => $pageLang->getDir(), - ); - $this->addHTML( Html::element( 'textarea', $params, $source ) ); - - // Show templates used by this article - $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); - $this->addHTML( "<div class='templatesUsed'> -$templates -</div> -" ); + public function readOnlyPage() { + if ( func_num_args() > 0 ) { + throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' ); } - # If the title doesn't exist, it's fairly pointless to print a return - # link to it. After all, you just tried editing it and couldn't, so - # what's there to do there? - if ( $this->getTitle()->exists() ) { - $this->returnToMain( null, $this->getTitle() ); - } + throw new ReadOnlyError; } /** * Turn off regular page output and return an error response * for when rate limiting has triggered. + * + * @deprecated since 1.25; throw the exception directly */ public function rateLimited() { + wfDeprecated( __METHOD__, '1.25' ); throw new ThrottledError; } @@ -2713,7 +2715,7 @@ $templates // Allow skins and extensions to add body attributes they need $sk->addToBodyAttributes( $this, $bodyAttrs ); - wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); + Hooks::run( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; @@ -2824,7 +2826,6 @@ $templates ); $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); - // Extract modules that know they're empty and see if we have one or more // raw modules $isRaw = false; @@ -2905,11 +2906,11 @@ $templates ); } else { $link = Html::linkedScript( $url ); - if ( $context->getOnly() === 'scripts' && !$context->getRaw() && !$isRaw ) { - // Wrap only=script requests in a conditional as browsers not supported - // by the startup module would unconditionally execute this module. - // Otherwise users will get "ReferenceError: mw is undefined" or - // "jQuery is undefined" from e.g. a "site" module. + if ( !$context->getRaw() && !$isRaw ) { + // Wrap only=script / only=combined requests in a conditional as + // browsers not supported by the startup module would unconditionally + // execute this module. Otherwise users will get "ReferenceError: mw is + // undefined" or "jQuery is undefined" from e.g. a "site" module. $link = Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( Xml::encodeJsCall( 'document.write', array( $link ) ) @@ -3108,7 +3109,7 @@ $templates // This also enforces $.isReady to be true at </body> which fixes the // mw.loader bug in Firefox with using document.write between </body> // and the DOMContentReady event (bug 47457). - $html = Html::inlineScript( 'window.jQuery && jQuery.ready();' ); + $html = Html::inlineScript( 'if(window.jQuery)jQuery.ready();' ); if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) { $html .= $this->getScriptsForBottomQueue( false ); @@ -3223,6 +3224,7 @@ $templates 'wgMonthNames' => $lang->getMonthNamesArray(), 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(), + 'wgRelevantArticleId' => $relevantTitle->getArticleId(), ); if ( $user->isLoggedIn() ) { @@ -3263,7 +3265,7 @@ $templates // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not // page-dependant but site-wide (without state). // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. - wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); + Hooks::run( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); // Merge in variables from addJsConfigVars last return array_merge( $vars, $this->getJsConfigVars() ); @@ -3313,6 +3315,13 @@ $templates 'content' => "MediaWiki $wgVersion", ) ); + if ( $config->get( 'ReferrerPolicy' ) !== false ) { + $tags['meta-referrer'] = Html::element( 'meta', array( + 'name' => 'referrer', + 'content' => $config->get( 'ReferrerPolicy' ) + ) ); + } + $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; if ( $p !== 'index,follow' ) { // http://www.robotstxt.org/wc/meta-user.html @@ -3615,7 +3624,9 @@ $templates $moduleStyles[] = 'user.groups'; // Per-user custom styles - if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { + if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage() + && $this->userCanPreview() + ) { // We're on a preview of a CSS subpage // Exclude this page from the user module in case it's in there (bug 26283) $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, @@ -3813,12 +3824,13 @@ $templates * This function takes a number of message/argument specifications, wraps them in * some overall structure, and then parses the result and adds it to the output. * - * In the $wrap, $1 is replaced with the first message, $2 with the second, and so - * on. The subsequent arguments may either be strings, in which case they are the - * message names, or arrays, in which case the first element is the message name, - * and subsequent elements are the parameters to that message. + * In the $wrap, $1 is replaced with the first message, $2 with the second, + * and so on. The subsequent arguments may be either + * 1) strings, in which case they are message names, or + * 2) arrays, in which case, within each array, the first element is the message + * name, and subsequent elements are the parameters to that message. * - * Don't use this for messages that are not in users interface language. + * Don't use this for messages that are not in the user's interface language. * * For example: * @@ -3829,7 +3841,7 @@ $templates * $wgOut->addWikiText( "<div class='error'>\n" * . wfMessage( 'some-error' )->plain() . "\n</div>" ); * - * The newline after opening div is needed in some wikitext. See bug 19226. + * The newline after the opening div is needed in some wikitext. See bug 19226. * * @param string $wrap */ @@ -3904,4 +3916,16 @@ $templates public function sectionEditLinksEnabled() { return $this->mEnableSectionEditLinks; } + + /** + * Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with + * MediaWiki and this OutputPage instance. + * + * @since 1.25 + */ + public function enableOOUI() { + OOUI\Theme::setSingleton( new OOUI\MediaWikiTheme() ); + OOUI\Element::setDefaultDir( $this->getLanguage()->getDir() ); + $this->addModuleStyles( 'oojs-ui.styles' ); + } } diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php new file mode 100644 index 00000000..eee9aa9c --- /dev/null +++ b/includes/PHPVersionCheck.php @@ -0,0 +1,157 @@ +<?php +/** + * Check PHP Version, as well as for composer dependencies in entry points, + * and display something vaguely comprehensible in the event of a totally + * unrecoverable error. + * + * 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 + */ + +/** + * Check php version and that external dependencies are installed, and + * display an informative error if either condition is not satisfied. + */ +function wfEntryPointCheck( $entryPoint ) { + if ( !function_exists( 'version_compare' ) + || version_compare( PHP_VERSION, '5.3.3' ) < 0 + || !file_exists( dirname( __FILE__ ) . '/../vendor/autoload.php' ) + ) { + wfPHPVersionError( $entryPoint ); + } +} + +/** + * Display something vaguely comprehensible in the event of a totally unrecoverable error. + * Does not assume access to *anything*; no globals, no autoloader, no database, no localisation. + * Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php + * no longer need to be). + * + * Calling this function kills execution immediately. + * + * @param string $type Which entry point we are protecting. One of: + * - index.php + * - load.php + * - api.php + * - mw-config/index.php + * - cli + * + * @note Since we can't rely on anything, the minimum PHP versions and MW current + * version are hardcoded here + */ +function wfPHPVersionError( $type ) { + $mwVersion = '1.25'; + $minimumVersionPHP = '5.3.3'; + + $phpVersion = PHP_VERSION; + $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; + $message = "MediaWiki $mwVersion requires at least " + . "PHP version $minimumVersionPHP, you are using PHP $phpVersion. Installing some " + . " external dependencies (e.g. via composer) is also required."; + + if ( $type == 'cli' ) { + $finalOutput = "Error: You are missing some external dependencies or are using on older PHP version. \n" + . "MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher.\n\n" + . "Check if you have a newer php executable with a different name, such as php5.\n\n" + . "MediaWiki now also has some external dependencies that need to be installed\n" + . "via composer or from a separate git repo. Please see\n" + . "https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries\n" + . "for help on installing the required components."; + } elseif ( $type == 'index.php' || $type == 'mw-config/index.php' ) { + $pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] ); + if ( $type == 'mw-config/index.php' ) { + $dirname = dirname( $pathinfo['dirname'] ); + } else { + $dirname = $pathinfo['dirname']; + } + $encLogo = htmlspecialchars( + str_replace( '//', '/', $dirname . '/' ) . + 'resources/assets/mediawiki.png' + ); + + header( "$protocol 500 MediaWiki configuration Error" ); + header( 'Content-type: text/html; charset=UTF-8' ); + // Don't cache error pages! They cause no end of trouble... + header( 'Cache-control: none' ); + header( 'Pragma: no-cache' ); + + $finalOutput = <<<HTML +<!DOCTYPE html> +<html lang="en" dir="ltr"> + <head> + <meta charset="UTF-8" /> + <title>MediaWiki {$mwVersion}</title> + <style media='screen'> + body { + color: #000; + background-color: #fff; + font-family: sans-serif; + padding: 2em; + text-align: center; + } + p, img, h1, h2 { + text-align: left; + margin: 0.5em 0 1em; + } + h1 { + font-size: 120%; + } + h2 { + font-size: 110%; + } + </style> + </head> + <body> + <img src="{$encLogo}" alt='The MediaWiki logo' /> + <h1>MediaWiki {$mwVersion} internal error</h1> + <div class='error'> + <p> + {$message} + </p> + <h2>Supported PHP versions</h2> + <p> + Please consider <a href="http://www.php.net/downloads.php">upgrading your copy of PHP</a>. + PHP versions less than 5.3.0 are no longer supported by the PHP Group and will not receive + security or bugfix updates. + </p> + <p> + If for some reason you are unable to upgrade your PHP version, you will need to + <a href="https://www.mediawiki.org/wiki/Download">download</a> an older version + of MediaWiki from our website. See our + <a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a> + for details of which versions are compatible with prior versions of PHP. + </p> + <h2>External dependencies</h2> + <p> + MediaWiki now also has some external dependencies that need to be installed via + composer or from a separate git repo. Please see + <a href="https://www.mediawiki.org/wiki/Download_from_Git#Fetch_external_libraries">mediawiki.org</a> + for help on installing the required components. + </p> + </div> + </body> +</html> +HTML; + // Handle everything that's not index.php + } else { + // So nothing thinks this is JS or CSS + $finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message; + header( "$protocol 500 MediaWiki configuration Error" ); + } + echo "$finalOutput\n"; + die( 1 ); +} diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php index f481650c..007ea894 100644 --- a/includes/PHPVersionError.php +++ b/includes/PHPVersionError.php @@ -1,6 +1,7 @@ <?php /** - * Display something vaguely comprehensible in the event of a totally unrecoverable error. + * Backwards compatibility. The PHP version error function is now + * included in PHPVersionCheck.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 @@ -17,110 +18,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @deprecated 1.25 * @file */ - -/** - * Display something vaguely comprehensible in the event of a totally unrecoverable error. - * Does not assume access to *anything*; no globals, no autoloader, no database, no localisation. - * Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php - * no longer need to be). - * - * Calling this function kills execution immediately. - * - * @param string $type Which entry point we are protecting. One of: - * - index.php - * - load.php - * - api.php - * - mw-config/index.php - * - cli - * - * @note Since we can't rely on anything, the minimum PHP versions and MW current - * version are hardcoded here - */ -function wfPHPVersionError( $type ) { - $mwVersion = '1.24'; - $minimumVersionPHP = '5.3.2'; - - $phpVersion = PHP_VERSION; - $protocol = isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; - $message = "MediaWiki $mwVersion requires at least " - . "PHP version $minimumVersionPHP, you are using PHP $phpVersion."; - - if ( $type == 'cli' ) { - $finalOutput = "You are using PHP version $phpVersion " - . "but MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher. ABORTING.\n" - . "Check if you have a newer php executable with a different name, such as php5.\n"; - } elseif ( $type == 'index.php' || $type == 'mw-config/index.php' ) { - $pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] ); - if ( $type == 'mw-config/index.php' ) { - $dirname = dirname( $pathinfo['dirname'] ); - } else { - $dirname = $pathinfo['dirname']; - } - $encLogo = htmlspecialchars( - str_replace( '//', '/', $dirname . '/' ) . - 'resources/assets/mediawiki.png' - ); - - header( "$protocol 500 MediaWiki configuration Error" ); - header( 'Content-type: text/html; charset=UTF-8' ); - // Don't cache error pages! They cause no end of trouble... - header( 'Cache-control: none' ); - header( 'Pragma: no-cache' ); - - $finalOutput = <<<HTML -<!DOCTYPE html> -<html lang="en" dir="ltr"> - <head> - <meta charset="UTF-8" /> - <title>MediaWiki {$mwVersion}</title> - <style media='screen'> - body { - color: #000; - background-color: #fff; - font-family: sans-serif; - padding: 2em; - text-align: center; - } - p, img, h1 { - text-align: left; - margin: 0.5em 0; - } - h1 { - font-size: 120%; - } - </style> - </head> - <body> - <img src="{$encLogo}" alt='The MediaWiki logo' /> - <h1>MediaWiki {$mwVersion} internal error</h1> - <div class='error'> - <p> - {$message} - </p> - <p> - Please consider <a href="http://www.php.net/downloads.php">upgrading your copy of PHP</a>. - PHP versions less than 5.3.0 are no longer supported by the PHP Group and will not receive - security or bugfix updates. - </p> - <p> - If for some reason you are unable to upgrade your PHP version, you will need to - <a href="https://www.mediawiki.org/wiki/Download">download</a> an older version - of MediaWiki from our website. See our - <a href="https://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a> - for details of which versions are compatible with prior versions of PHP. - </p> - </div> - </body> -</html> -HTML; - // Handle everything that's not index.php - } else { - // So nothing thinks this is JS or CSS - $finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message; - header( "$protocol 500 MediaWiki configuration Error" ); - } - echo "$finalOutput\n"; - die( 1 ); -} +require_once dirname( __FILE__ ) . '/PHPVersionCheck.php'; diff --git a/includes/Preferences.php b/includes/Preferences.php index 84cf5af0..a5239331 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -96,7 +96,7 @@ class Preferences { self::searchPreferences( $user, $context, $defaultPreferences ); self::miscPreferences( $user, $context, $defaultPreferences ); - wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) ); + Hooks::run( 'GetPreferences', array( $user, &$defaultPreferences ) ); self::loadPreferenceValues( $user, $context, $defaultPreferences ); self::$defaultPreferences = $defaultPreferences; @@ -130,8 +130,7 @@ class Preferences { if ( $disable && !in_array( $name, self::$saveBlacklist ) ) { $info['disabled'] = 'disabled'; } - $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation - $field->mParent = $dummyForm; + $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation $defaultOptions = User::getDefaultOptions(); $globalDefault = isset( $defaultOptions[$name] ) ? $defaultOptions[$name] @@ -244,10 +243,9 @@ class Preferences { 'type' => 'info', 'label' => $context->msg( 'prefs-memberingroups' )->numParams( count( $userGroups ) )->params( $userName )->parse(), - 'default' => $context->msg( 'prefs-memberingroups-type', - $lang->commaList( $userGroups ), - $lang->commaList( $userMembers ) - )->plain(), + 'default' => $context->msg( 'prefs-memberingroups-type' ) + ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) ) + ->escaped(), 'raw' => true, 'section' => 'personal/info', ); @@ -339,11 +337,11 @@ class Preferences { 'type' => 'radio', 'section' => 'personal/i18n', 'options' => array( - $context->msg( 'parentheses', - $context->msg( 'gender-unknown' )->text() - )->text() => 'unknown', - $context->msg( 'gender-female' )->text() => 'female', - $context->msg( 'gender-male' )->text() => 'male', + $context->msg( 'parentheses' ) + ->params( $context->msg( 'gender-unknown' )->plain() ) + ->escaped() => 'unknown', + $context->msg( 'gender-female' )->escaped() => 'female', + $context->msg( 'gender-male' )->escaped() => 'male', ), 'label-message' => 'yourgender', 'help-message' => 'prefs-help-gender', @@ -451,8 +449,8 @@ class Preferences { array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) ); $emailAddress .= $emailAddress == '' ? $link : ( - $context->msg( 'word-separator' )->plain() - . $context->msg( 'parentheses' )->rawParams( $link )->plain() + $context->msg( 'word-separator' )->escaped() + . $context->msg( 'parentheses' )->rawParams( $link )->escaped() ); } @@ -870,7 +868,7 @@ class Preferences { 'min' => 1, 'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ), 'help' => $context->msg( 'recentchangesdays-max' )->numParams( - ceil( $rcMaxAge / ( 3600 * 24 ) ) )->text() + ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped() ); $defaultPreferences['rclimit'] = array( 'type' => 'int', @@ -895,6 +893,9 @@ class Preferences { 'section' => 'rc/advancedrc', 'label-message' => 'tog-hidepatrolled', ); + } + + if ( $user->useNPPatrol() ) { $defaultPreferences['newpageshidepatrolled'] = array( 'type' => 'toggle', 'section' => 'rc/advancedrc', @@ -921,13 +922,37 @@ class Preferences { $watchlistdaysMax = ceil( $config->get( 'RCMaxAge' ) / ( 3600 * 24 ) ); ## Watchlist ##################################### + if ( $user->isAllowed( 'editmywatchlist' ) ) { + $editWatchlistLinks = array(); + $editWatchlistModes = array( + 'edit' => array( 'EditWatchlist', false ), + 'raw' => array( 'EditWatchlist', 'raw' ), + 'clear' => array( 'EditWatchlist', 'clear' ), + ); + foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) { + // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear + $editWatchlistLinks[] = Linker::linkKnown( + SpecialPage::getTitleFor( $mode[0], $mode[1] ), + $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() + ); + } + + $defaultPreferences['editwatchlist'] = array( + 'type' => 'info', + 'raw' => true, + 'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ), + 'label-message' => 'prefs-editwatchlist-label', + 'section' => 'watchlist/editwatchlist', + ); + } + $defaultPreferences['watchlistdays'] = array( 'type' => 'float', 'min' => 0, 'max' => $watchlistdaysMax, 'section' => 'watchlist/displaywatchlist', 'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams( - $watchlistdaysMax )->text(), + $watchlistdaysMax )->escaped(), 'label-message' => 'prefs-watchlist-days', ); $defaultPreferences['wllimit'] = array( @@ -969,7 +994,7 @@ class Preferences { 'label-message' => 'tog-watchlisthideliu', ); - if ( $context->getConfig()->get( 'UseRCPatrol' ) ) { + if ( $user->useRCPatrol() ) { $defaultPreferences['watchlisthidepatrolled'] = array( 'type' => 'toggle', 'section' => 'watchlist/advancedwatchlist', @@ -1047,7 +1072,7 @@ class Preferences { $ret = array(); $mptitle = Title::newMainPage(); - $previewtext = $context->msg( 'skin-preview' )->text(); + $previewtext = $context->msg( 'skin-preview' )->escaped(); # Only show skins that aren't disabled in $wgSkipSkins $validSkinNames = Skin::getAllowedSkins(); @@ -1072,7 +1097,7 @@ class Preferences { $linkTools = array(); # Mark the default skin - if ( $skinkey == $defaultSkin ) { + if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) { $linkTools[] = $context->msg( 'default' )->escaped(); $foundDefault = true; } @@ -1092,10 +1117,9 @@ class Preferences { $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() ); } - $display = $sn . ' ' . $context->msg( - 'parentheses', - $context->getLanguage()->pipeList( $linkTools ) - )->text(); + $display = $sn . ' ' . $context->msg( 'parentheses' ) + ->rawParams( $context->getLanguage()->pipeList( $linkTools ) ) + ->escaped(); $ret[$display] = $skinkey; } @@ -1433,7 +1457,7 @@ class Preferences { $user->setOption( $key, $value ); } - wfRunHooks( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) ); + Hooks::run( 'PreferencesFormPreSave', array( $formData, $form, $user, &$result ) ); $user->saveSettings(); } @@ -1466,28 +1490,6 @@ class Preferences { return Status::newGood(); } - - /** - * Try to set a user's email address. - * This does *not* try to validate the address. - * Caller is responsible for checking $wgAuth and 'editmyprivateinfo' - * right. - * - * @deprecated since 1.20; use User::setEmailWithConfirmation() instead. - * @param User $user - * @param string $newaddr New email address - * @return array (true on success or Status on failure, info string) - */ - public static function trySetUserEmail( User $user, $newaddr ) { - wfDeprecated( __METHOD__, '1.20' ); - - $result = $user->setEmailWithConfirmation( $newaddr ); - if ( $result->isGood() ) { - return array( true, $result->value ); - } else { - return array( $result, 'mailerror' ); - } - } } /** Some tweaks to allow js prefs to work */ @@ -1539,12 +1541,8 @@ class PreferencesForm extends HTMLForm { * @return string */ function getButtons() { - global $wgUseMediaWikiUIEverywhere; $attrs = array( 'id' => 'mw-prefs-restoreprefs' ); - if ( $wgUseMediaWikiUIEverywhere ) { - $attrs['class'] = 'mw-ui-button mw-ui-quiet'; - } if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) { return ''; @@ -1556,7 +1554,7 @@ class PreferencesForm extends HTMLForm { $t = SpecialPage::getTitleFor( 'Preferences', 'reset' ); $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped(), - $attrs ); + Html::buttonAttributes( $attrs, array( 'mw-ui-quiet' ) ) ); $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html ); } @@ -1601,7 +1599,7 @@ class PreferencesForm extends HTMLForm { */ function getLegend( $key ) { $legend = parent::getLegend( $key ); - wfRunHooks( 'PreferencesGetLegend', array( $this, $key, &$legend ) ); + Hooks::run( 'PreferencesGetLegend', array( $this, $key, &$legend ) ); return $legend; } } diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index 718750f5..55a4f49b 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -34,11 +34,12 @@ abstract class PrefixSearch { * @param string $search * @param int $limit * @param array $namespaces Used if query is not explicit |