From: jenkins-bot Date: Thu, 18 Sep 2014 02:27:56 +0000 (+0000) Subject: Merge "Don't break existing expiry time of "infinity" when modifying protection" X-Git-Tag: 1.31.0-rc.0~13969 X-Git-Url: http://git.cyclocoop.org/%22%2C%20generer_url_ecrire%28?a=commitdiff_plain;h=523c6c0e41159c073b6b44dd550878f42cbc847e;hp=e0400a2da02a06896944812af8439e411e0755f7;p=lhc%2Fweb%2Fwiklou.git Merge "Don't break existing expiry time of "infinity" when modifying protection" --- diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index c5b6cd45fd..d2419fa82b 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -70,6 +70,10 @@ production. we will send rel=alternate. === New features in 1.24 === +* Added new hook WatchlistEditorBeforeFormRender, allowing subscribers to + manipulate the list of pages and/or preload lots of data at once. +* Added new argument &$link in hook WatchlistEditorBuildRemoveLine, allowing the + link to the title to be changed. * Added a new hook, "WhatLinksHereProps", to allow extensions to annotate WhatLinksHere entries. * Added a new hook, "ContentGetParserOutput", to customize parser output for @@ -142,7 +146,7 @@ production. Special:PageLanguage. All pages are set to wiki language by default. The feature needs to be enabled with $wgPageLanguageUseDB=true and permission needs to be set for 'pagelang'. -* Upgrade Moment.js to v2.8.1. +* Upgrade Moment.js to v2.8.3. * (bug 67042) Added support for the HTML5 tag for East Asian typography. * Upgrade Sinon.JS to 1.10.3. * Added the es5-shim polyfill for older or non-compliant javascript engines. @@ -170,13 +174,15 @@ production. * (bug 15484) Users will now be redirected to the login page when they need to log in, rather than being shown a page asking them to log in and having to click another link to actually get to the login page. -* A JSONContent and JSONContentHandler were added for extensions to extend. +* A JsonContent and JsonContentHandler were added for extensions to extend. * (bug 35045) Redirects to sections will now update the URL in browser's address bar using the HTML5 History API. When [[Dog]] redirects to [[Animals#Dog]], the user will now see "Animals#Dog" in their browser instead of "Dog#Dog". * API token handling has been rewritten. Any API module using tokens will need to be updated. See the entry below under "Action API internal changes". * Added HTMLAutoCompleteSelectField. +* Added a new hook, "SkinPreloadExistence", to allow extensions to add titles to + link existence cache before the page is rendered. === Bug fixes in 1.24 === * (bug 50572) MediaWiki:Blockip should support gender @@ -209,6 +215,9 @@ production. * (bugs 57238, 65206) Blank pages can now be directly created. * (bug 69789) Title::getContentModel() now loads from the database when necessary instead of incorrectly returning the default content model. +* (bug 69249) wfBaseConvert() now works around PHP Bug #50175 when using GMP. +* (bug 57909) URLs in the externallinks table will no longer have certain + characters decoded in the query string. === Action API changes in 1.24 === * action=parse API now supports prop=modules, which provides the list of @@ -246,6 +255,11 @@ production. of fetching tokens are deprecated. The value needed for meta=tokens's 'type' parameter for each module is documented in the action=help output and is returned from action=paraminfo. +* New action ClearHasMsg that can be used to clear HasMsg flag. +* The cmstartsortkey and cmendsortkey parameters to list=categorymembers are + deprecated in favor of cmstarthexsortkey and cmendhexsortkey. +* (bug 63326) Add blockedtimestamp field to output of blockinfo property for + the list=allusers and list=users modules. === Action API internal changes in 1.24 === * Methods for handling continuation are added to ApiResult, so actions other @@ -348,6 +362,13 @@ changes to languages because of Bugzilla reports. the "headelement" template key are no longer supported. Setting $useHeadElement = false; is no longer supported and will not cause old keys like "headlinks", "skinnameclass", etc. to be defined. +* BREAKING CHANGE: The files commonElements.css, commonContent.css and + commonInterface.css (in skins/common/) have been removed. Skins may no longer + rely on their presence and include them in their style modules. ResourceLoader + modules introduced in MediaWiki 1.23 should be loaded instead: + - skins/common/commonElements.css → 'mediawiki.skinning.elements' module + - skins/common/commonContent.css → 'mediawiki.skinning.content' module + - skins/common/commonInterface.css → 'mediawiki.skinning.interface' module * The deprecated 'SpecialVersionExtensionTypes' hook was removed. * (bug 63891) Add 'X-Robots-Tag: noindex' header in action=render pages. * SpecialPage no longer supports the syntax for invoking wfSpecial*() functions. @@ -415,8 +436,8 @@ changes to languages because of Bugzilla reports. setPreloadedText() from EditPage.php. (deprecated since 1.21) * Removed global functions wfArrayLookup(), wfArrayMerge(), wfDebugDieBacktrace() and wfTime(). (deprecated since 1.22) -* Browser support for Internet Explorer 6 lowered from Grade A to Grade C, - meaning that JavaScript is no longer executed in this browser. +* Browser support for Internet Explorer 6 and 7 lowered from Grade A to Grade C, + meaning that JavaScript is no longer executed in these browser versions. * Browser support for Opera 11 lowered from Grade A to Grade C. * Removed IEFixes module which existed purely to provide support for MSIE versions below 7 (conditionally loaded only for those browsers). @@ -434,6 +455,17 @@ changes to languages because of Bugzilla reports. called unconditionally. * TablePager::getBody() is now 'final' and can't be overridden in subclasses. * TablePager::getBody() is deprecated, use getBodyOutput() or getFullOutput(). +* Added $outputPage parameter to the SkinTemplateGetLanguageLink hook. +* log_page for move log entries store the original page ID, rather than that + of the new redirect page. This is not retroactive. +* LCStoreAccel was removed. $wgLocalisationCacheConf can no longer be set to + use this store class. +* Html::infoBox() no longer accepts paths relative to skins/common/images/. +* Deprecated defunct Skin::getCommonStylePath(). +* Some extensions had their ResourceLoader modules depend on the "mediawiki" + and "jquery" modules. In the past, this behavior was undefined, now it will + throw an error. +* Removed BagOStuff::replace(). (deprecated since 1.23) ==== Renamed classes ==== * CLDRPluralRuleConverter_Expression to CLDRPluralRuleConverterExpression @@ -485,7 +517,8 @@ changes to languages because of Bugzilla reports. == Compatibility == -MediaWiki 1.24 requires PHP 5.3.2 or later. +MediaWiki 1.24 requires PHP 5.3.2 or later. There is experimental support for +HHVM 3.3.0. MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but support for them is somewhat less mature. There is experimental support for diff --git a/docs/hooks.txt b/docs/hooks.txt index ad5377efb1..f4b8ef2b98 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -863,6 +863,14 @@ $name: name of the special page, e.g. 'Watchlist' &$join_conds: join conditions for the tables $opts: FormOptions for this request +'LoginUserMigrated': Called during login to allow extensions the opportunity to +inform a user that their username doesn't exist for a specific reason, instead +of letting the login form give the generic error message that the account does +not exist. For example, when the account has been renamed or deleted. +$user: the User object being authenticated against. +&$msg: the message identifier for abort reason, or an array to pass a message + key and parameters. + 'Collation::factory': Called if $wgCategoryCollation is an unknown collation. $collationName: Name of the collation in question &$collationObject: Null. Replace with a subclass of the Collation class that @@ -1542,6 +1550,10 @@ $title: The page's Title. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. +'LanguageSelector': Hook to change the language selector available on a page. +$out: The output page. +$cssClassName: CSS class name of the language selector. + 'LinkBegin': Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on @@ -1635,6 +1647,13 @@ $code: language code &$alldata: The localisation data from core and extensions &purgeBlobs: whether to purge/update the message blobs via MessageBlobStore::clear() +'LocalisationCacheRecacheFallback': Called for each language when merging +fallback data into the cache. +$cache: The LocalisationCache object +$code: language code +&$alldata: The localisation data from core and extensions. Note some keys may + be omitted if they won't be merged into the final result. + 'LocalisationChecksBlacklist': When fetching the blacklist of localisation checks. &$blacklist: array of checks to blacklist. See the bottom of @@ -2292,6 +2311,11 @@ $type: 'normal' or 'history' for old/diff views the MediaWiki icon but plain text instead. $skin: Skin object +'SkinPreloadExistence': Supply titles that should be added to link existence +cache before the page is rendered. +&$titles: Array of Title objects +$skin: Skin object + 'SkinSubPageSubtitle': At the beginning of Skin::subPageSubtitle(). &$subpages: Subpage links HTML $skin: Skin object @@ -2310,8 +2334,9 @@ $nav_urls: array of tabs which the actual html is constructed. &$languageLink: array containing data about the link. The following keys can be modified: href, text, title, class, lang, hreflang. Each of them is a string. -$languageLinkTitle: Title object belonging to the external language link -$title: Title object of the page the link belongs to +$languageLinkTitle: Title object belonging to the external language link. +$title: Title object of the page the link belongs to. +$outputPage: The OutputPage object the links are built from. To alter the structured navigation links in SkinTemplates, there are three hooks called in different spots: @@ -2569,6 +2594,10 @@ database result. &$titleArray: set this to an object to override the default object returned $res: database result used to create the object +'TitleExists': Called when determining whether a page exists at a given title. +$title: The title being tested. +&$exists: Whether the title exists. + 'TitleQuickPermissions': Called from Title::checkQuickPermissions to add to or override the quick permissions check. $title: The Title object being accessed @@ -2942,12 +2971,18 @@ $page: WikiPage object to be watched $user: user that watched $page: WikiPage object watched +'WatchlistEditorBeforeFormRender': Before building the Special:EditWatchlist +form, used to manipulate the list of pages or preload data based on that list. +&$watchlistInfo: array of watchlisted pages in + [namespaceId => ['title1' => 1, 'title2' => 1]] format + 'WatchlistEditorBuildRemoveLine': when building remove lines in Special:Watchlist/edit. &$tools: array of extra links $title: Title object $redirect: whether the page is a redirect $skin: Skin object +&$link: HTML link to title 'WebRequestPathInfoRouter': While building the PathRouter to parse the REQUEST_URI. diff --git a/docs/kss/Makefile b/docs/kss/Makefile index ff735d5180..a7b0c47122 100644 --- a/docs/kss/Makefile +++ b/docs/kss/Makefile @@ -4,7 +4,9 @@ kss: kssnodecheck # Generates CSS of mediawiki.ui and mediawiki.ui.button using ResourceLoader, then applies it to the # KSS style guide $(eval KSS_RL_TMP := $(shell mktemp /tmp/tmp.XXXXXXXXXX)) - @curl -sG "${MEDIAWIKI_LOAD_URL}?modules=mediawiki.ui.anchor|mediawiki.ui.checkbox|mediawiki.ui.input|mediawiki.legacy.shared|mediawiki.legacy.commonPrint|mediawiki.ui|mediawiki.ui.button&only=styles" > $(KSS_RL_TMP) +# Keep module names in strict alphabetical order, so CSS loads in the same order as ResourceLoader's addModuleStyles does; this can affect rendering. +# See OutputPage::makeResourceLoaderLink. + @curl -sG "${MEDIAWIKI_LOAD_URL}?modules=mediawiki.legacy.commonPrint|mediawiki.legacy.shared|mediawiki.ui|mediawiki.ui.anchor|mediawiki.ui.button|mediawiki.ui.checkbox|mediawiki.ui.input&only=styles" > $(KSS_RL_TMP) @node_modules/.bin/kss-node ../../resources/src/mediawiki.ui static/ --css $(KSS_RL_TMP) -t styleguide-template @rm $(KSS_RL_TMP) diff --git a/docs/kss/styleguide-template/index.html b/docs/kss/styleguide-template/index.html index b6036b2d94..933260ec9c 100644 --- a/docs/kss/styleguide-template/index.html +++ b/docs/kss/styleguide-template/index.html @@ -19,25 +19,42 @@
-
diff --git a/docs/kss/styleguide-template/public/kss.less b/docs/kss/styleguide-template/public/kss.less index f5ddff1253..c30322e6d5 100644 --- a/docs/kss/styleguide-template/public/kss.less +++ b/docs/kss/styleguide-template/public/kss.less @@ -1,4 +1,3 @@ - .container { width: 100%; } @@ -7,7 +6,7 @@ nav { display: none; } -article { +.content { .example { blockquote { margin-top: 20px; @@ -25,7 +24,8 @@ body { font-family: "Nimbus Sans L", "Liberation Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; } -.content.kss-no-margin { +.kss-no-margin { + // FIXME: Is this being used anywhere? Remove if not. margin: 0; } @@ -85,11 +85,22 @@ nav { width: 35px; } } + + ul { + li { + margin: 0; + } + + li a { + text-transform: none; + font-weight: normal; + } + } } } } -article { +.content { -webkit-flex: 1; flex: 1; @@ -154,7 +165,7 @@ article { width: auto; } - article { + .content { margin-left: 30px; } diff --git a/img_auth.php b/img_auth.php index 55f17ac7f4..dcd171f94e 100644 --- a/img_auth.php +++ b/img_auth.php @@ -47,6 +47,7 @@ $wgArticlePath = false; # Don't let a "/*" article path clober our action path $wgActionPaths = array( "$wgUploadPath/" ); wfImageAuthMain(); +wfProfileOut( 'img_auth.php' ); wfLogProfilingData(); // Commit and close up! $factory = wfGetLBFactory(); diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 661f4d6410..f8617c0228 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -36,6 +36,10 @@ $wgAutoloadLocalClasses = array( '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', @@ -64,7 +68,7 @@ $wgAutoloadLocalClasses = array( 'DumpOutput' => 'includes/Export.php', 'DumpPipeOutput' => 'includes/Export.php', 'EditPage' => 'includes/EditPage.php', - 'EmailNotification' => 'includes/UserMailer.php', + 'EmptyBloomCache' => 'includes/cache/bloom/BloomCache.php', 'Fallback' => 'includes/Fallback.php', 'FauxRequest' => 'includes/WebRequest.php', 'FauxResponse' => 'includes/WebResponse.php', @@ -117,12 +121,12 @@ $wgAutoloadLocalClasses = array( 'LinkFilter' => 'includes/LinkFilter.php', 'MagicWord' => 'includes/MagicWord.php', 'MagicWordArray' => 'includes/MagicWord.php', - 'MailAddress' => 'includes/UserMailer.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', @@ -170,7 +174,6 @@ $wgAutoloadLocalClasses = array( 'User' => 'includes/User.php', 'UserArray' => 'includes/UserArray.php', 'UserArrayFromResult' => 'includes/UserArrayFromResult.php', - 'UserMailer' => 'includes/UserMailer.php', 'UserRightsProxy' => 'includes/UserRightsProxy.php', 'WatchedItem' => 'includes/WatchedItem.php', 'WebRequest' => 'includes/WebRequest.php', @@ -214,6 +217,7 @@ $wgAutoloadLocalClasses = array( # 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', @@ -333,7 +337,6 @@ $wgAutoloadLocalClasses = array( 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php', 'ICacheHelper' => 'includes/cache/CacheHelper.php', 'LCStore' => 'includes/cache/LocalisationCache.php', - 'LCStoreAccel' => 'includes/cache/LocalisationCache.php', 'LCStoreCDB' => 'includes/cache/LocalisationCache.php', 'LCStoreDB' => 'includes/cache/LocalisationCache.php', 'LCStoreNull' => 'includes/cache/LocalisationCache.php', @@ -369,23 +372,25 @@ $wgAutoloadLocalClasses = array( 'ConfigException' => 'includes/config/ConfigException.php', 'ConfigFactory' => 'includes/config/ConfigFactory.php', 'GlobalVarConfig' => 'includes/config/GlobalVarConfig.php', + 'MutableConfig' => 'includes/config/MutableConfig.php', # includes/content 'AbstractContent' => 'includes/content/AbstractContent.php', - 'ContentHandler' => 'includes/content/ContentHandler.php', + 'CodeContentHandler' => 'includes/content/CodeContentHandler.php', 'Content' => 'includes/content/Content.php', - 'CssContentHandler' => 'includes/content/CssContentHandler.php', + 'ContentHandler' => 'includes/content/ContentHandler.php', 'CssContent' => 'includes/content/CssContent.php', - 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php', + 'CssContentHandler' => 'includes/content/CssContentHandler.php', 'JavaScriptContent' => 'includes/content/JavaScriptContent.php', - 'JSONContentHandler' => 'includes/content/JSONContentHandler.php', - 'JSONContent' => 'includes/content/JSONContent.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', - 'TextContentHandler' => 'includes/content/TextContentHandler.php', 'TextContent' => 'includes/content/TextContent.php', - 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', + 'TextContentHandler' => 'includes/content/TextContentHandler.php', 'WikitextContent' => 'includes/content/WikitextContent.php', + 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', # includes/context 'ContextSource' => 'includes/context/ContextSource.php', @@ -719,6 +724,11 @@ $wgAutoloadLocalClasses = array( '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', @@ -745,6 +755,7 @@ $wgAutoloadLocalClasses = array( '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', @@ -869,6 +880,7 @@ $wgAutoloadLocalClasses = array( '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', diff --git a/includes/CategoryFinder.php b/includes/CategoryFinder.php index 9fd388352a..cf537e15e5 100644 --- a/includes/CategoryFinder.php +++ b/includes/CategoryFinder.php @@ -114,6 +114,14 @@ class CategoryFinder { return $ret; } + /** + * Get the parents. Only really useful if run() has been called already + * @return array + */ + public function getParents() { + return $this->parents; + } + /** * This functions recurses through the parent representation, trying to match the conditions * @param int $id The article/category to check diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index 60694c95e3..7581ae40d4 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -25,10 +25,10 @@ class CategoryViewer extends ContextSource { public $limit; /** @var array */ - protected $from; + public $from; /** @var array */ - protected $until; + public $until; /** @var string[] */ public $articles; @@ -37,37 +37,37 @@ class CategoryViewer extends ContextSource { public $articles_start_char; /** @var array */ - protected $children; + public $children; /** @var array */ - protected $children_start_char; + public $children_start_char; /** @var bool */ - protected $showGallery; + public $showGallery; /** @var array */ - protected $imgsNoGallery_start_char; + public $imgsNoGallery_start_char; /** @var array */ - protected $imgsNoGallery; + public $imgsNoGallery; /** @var array */ - protected $nextPage; + public $nextPage; /** @var array */ protected $prevPage; /** @var array */ - protected $flip; + public $flip; /** @var Title */ - protected $title; + public $title; /** @var Collation */ - protected $collation; + public $collation; /** @var ImageGallery */ - protected $gallery; + public $gallery; /** @var Category Category object for this page. */ private $cat; diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 5fc73776a9..2123af1070 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -859,7 +859,7 @@ $wgContentHandlers = array( // dumb version, no syntax highlighting CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // simple implementation, for use by extensions, etc. - CONTENT_MODEL_JSON => 'JSONContentHandler', + CONTENT_MODEL_JSON => 'JsonContentHandler', // dumb version, no syntax highlighting CONTENT_MODEL_CSS => 'CssContentHandler', // plain text, for use by extensions, etc. @@ -1156,7 +1156,7 @@ $wgMimeInfoFile = 'includes/mime.info'; * Sets an external MIME detector program. The command must print only * the MIME type to standard output. * The name of the file to process will be appended to the command given here. - * If not set or NULL, mime_content_type will be used if available. + * If not set or NULL, PHP's fileinfo extension will be used if available. * * @par Example: * @code @@ -2075,6 +2075,28 @@ $wgObjectCaches = array( 'hash' => array( 'class' => 'HashBagOStuff' ), ); +/** + * Map of bloom filter store names to configuration arrays. + * + * Example: + * $wgBloomFilterStores['main'] = array( + * 'cacheId' => 'main-v1', + * 'class' => 'BloomCacheRedis', + * 'redisServers' => array( '127.0.0.1:6379' ), + * 'redisConfig' => array( 'connectTimeout' => 2 ) + * ); + * + * A primary bloom filter must be created manually. + * Example in eval.php: + * + * BloomCache::get( 'main' )->init( 'shared', 1000000000, .001 ); + * + * The size should be as large as practical given wiki size and resources. + * + * @since 1.24 + */ +$wgBloomFilterStores = array(); + /** * The expiry time for the parser cache, in seconds. * The default is 86400 (one day). @@ -3080,7 +3102,7 @@ $wgFooterIcons = array( ), "poweredby" => array( "mediawiki" => array( - "src" => null, // Defaults to "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" + "src" => null, // Defaults to "$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png" "url" => "//www.mediawiki.org/", "alt" => "Powered by MediaWiki", ) @@ -3269,10 +3291,7 @@ $wgResourceModuleSkinStyles = array(); * * @par Example: * @code - * $wgResourceLoaderSources['foo'] = array( - * 'loadScript' => 'http://example.org/w/load.php', - * 'apiScript' => 'http://example.org/w/api.php' - * ); + * $wgResourceLoaderSources['foo'] = 'http://example.org/w/load.php'; * @endcode */ $wgResourceLoaderSources = array(); @@ -3855,6 +3874,12 @@ $wgMaxPPExpandDepth = 40; /** * URL schemes that should be recognized as valid by wfParseUrl(). + * + * WARNING: Do not add 'file:' to this or internal file links will be broken. + * Instead, if you want to support file links, add 'file://'. The same applies + * to any other protocols with the same name as a namespace. See bug #44011 for + * more information. + * * @see wfParseUrl */ $wgUrlProtocols = array( diff --git a/includes/EditPage.php b/includes/EditPage.php index a14191a035..a9925ffeca 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -185,7 +185,7 @@ class EditPage { private $mContextTitle = null; /** @var string */ - protected $action = 'submit'; + public $action = 'submit'; /** @var bool */ public $isConflict = false; @@ -200,13 +200,13 @@ class EditPage { public $isJsSubpage = false; /** @var bool */ - protected $isWrongCaseCssJsPage = false; + public $isWrongCaseCssJsPage = false; /** @var bool New page or new section */ - protected $isNew = false; + public $isNew = false; /** @var bool */ - protected $deletedSinceEdit; + public $deletedSinceEdit; /** @var string */ public $formtype; @@ -215,39 +215,34 @@ class EditPage { public $firsttime; /** @var bool|stdClass */ - protected $lastDelete; + public $lastDelete; - /** @var bool - * This is public because SemanticForms uses it (bug 67522). - * However, please consider using this property publicly - * to be deprecated. - * @protected - */ + /** @var bool */ public $mTokenOk = false; /** @var bool */ - protected $mTokenOkExceptSuffix = false; + public $mTokenOkExceptSuffix = false; /** @var bool */ - protected $mTriedSave = false; + public $mTriedSave = false; /** @var bool */ - protected $incompleteForm = false; + public $incompleteForm = false; /** @var bool */ - protected $tooBig = false; + public $tooBig = false; /** @var bool */ - protected $kblength = false; + public $kblength = false; /** @var bool */ - protected $missingComment = false; + public $missingComment = false; /** @var bool */ - protected $missingSummary = false; + public $missingSummary = false; /** @var bool */ - protected $allowBlankSummary = false; + public $allowBlankSummary = false; /** @var bool */ protected $blankArticle = false; @@ -256,19 +251,19 @@ class EditPage { protected $allowBlankArticle = false; /** @var string */ - protected $autoSumm = ''; + public $autoSumm = ''; /** @var string */ public $hookError = ''; /** @var ParserOutput */ - protected $mParserOutput; + public $mParserOutput; /** @var bool Has a summary been preset using GET parameter &summary= ? */ - protected $hasPresetSummary = false; + public $hasPresetSummary = false; /** @var bool */ - protected $mBaseRevision = false; + public $mBaseRevision = false; /** @var bool */ public $mShowSummaryField = true; @@ -282,16 +277,16 @@ class EditPage { public $preview = false; /** @var bool */ - protected $diff = false; + public $diff = false; /** @var bool */ public $minoredit = false; /** @var bool */ - protected $watchthis = false; + public $watchthis = false; /** @var bool */ - protected $recreate = false; + public $recreate = false; /** @var string */ public $textbox1 = ''; @@ -303,7 +298,7 @@ class EditPage { public $summary = ''; /** @var bool */ - protected $nosummary = false; + public $nosummary = false; /** @var string */ public $edittime = ''; @@ -315,13 +310,13 @@ class EditPage { public $sectiontitle = ''; /** @var string */ - protected $starttime = ''; + public $starttime = ''; /** @var int */ public $oldid = 0; /** @var string */ - protected $editintro = ''; + public $editintro = ''; /** @var null */ public $scrolltop = null; @@ -1897,8 +1892,14 @@ class EditPage { ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) | ( $bot ? EDIT_FORCE_BOT : 0 ); - $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags, - false, null, $this->contentFormat ); + $doEditStatus = $this->mArticle->doEditContent( + $content, + $this->summary, + $flags, + false, + null, + $content->getDefaultFormat() + ); if ( !$doEditStatus->isOK() ) { // Failure from doEdit() @@ -1943,9 +1944,7 @@ class EditPage { // Do this in its own transaction to reduce contention... $dbw = wfGetDB( DB_MASTER ); $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) { - $dbw->begin( $fname ); WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser ); - $dbw->commit( $fname ); } ); } } @@ -3231,6 +3230,7 @@ HTML $attrs['class'] = 'mw-ui-button mw-ui-quiet'; } $edithelp = Html::element( 'a', $attrs, wfMessage( 'edithelp' )->text() ) . + wfMessage( 'word-separator' )->escaped() . wfMessage( 'newwindow' )->parse(); $wgOut->addHTML( " {$cancel}\n" ); @@ -3553,22 +3553,22 @@ HTML * @return string */ static function getEditToolbar() { - global $wgStylePath, $wgContLang, $wgLang, $wgOut; + global $wgContLang, $wgOut; global $wgEnableUploads, $wgForeignFileRepos; $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos ); /** * $toolarray is an array of arrays each of which includes the - * filename of the button image (without path), the opening - * tag, the closing tag, optionally a sample text that is + * opening tag, the closing tag, optionally a sample text that is * inserted between the two when no selection is highlighted * and. The tip text is shown when the user moves the mouse * over the button. + * + * Images are defined in ResourceLoaderEditToolbarModule. */ $toolarray = array( array( - 'image' => $wgLang->getImageFile( 'button-bold' ), 'id' => 'mw-editbutton-bold', 'open' => '\'\'\'', 'close' => '\'\'\'', @@ -3576,7 +3576,6 @@ HTML 'tip' => wfMessage( 'bold_tip' )->text(), ), array( - 'image' => $wgLang->getImageFile( 'button-italic' ), 'id' => 'mw-editbutton-italic', 'open' => '\'\'', 'close' => '\'\'', @@ -3584,7 +3583,6 @@ HTML 'tip' => wfMessage( 'italic_tip' )->text(), ), array( - 'image' => $wgLang->getImageFile( 'button-link' ), 'id' => 'mw-editbutton-link', 'open' => '[[', 'close' => ']]', @@ -3592,7 +3590,6 @@ HTML 'tip' => wfMessage( 'link_tip' )->text(), ), array( - 'image' => $wgLang->getImageFile( 'button-extlink' ), 'id' => 'mw-editbutton-extlink', 'open' => '[', 'close' => ']', @@ -3600,7 +3597,6 @@ HTML 'tip' => wfMessage( 'extlink_tip' )->text(), ), array( - 'image' => $wgLang->getImageFile( 'button-headline' ), 'id' => 'mw-editbutton-headline', 'open' => "\n== ", 'close' => " ==\n", @@ -3608,7 +3604,6 @@ HTML 'tip' => wfMessage( 'headline_tip' )->text(), ), $imagesAvailable ? array( - 'image' => $wgLang->getImageFile( 'button-image' ), 'id' => 'mw-editbutton-image', 'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':', 'close' => ']]', @@ -3616,7 +3611,6 @@ HTML 'tip' => wfMessage( 'image_tip' )->text(), ) : false, $imagesAvailable ? array( - 'image' => $wgLang->getImageFile( 'button-media' ), 'id' => 'mw-editbutton-media', 'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':', 'close' => ']]', @@ -3624,7 +3618,6 @@ HTML 'tip' => wfMessage( 'media_tip' )->text(), ) : false, array( - 'image' => $wgLang->getImageFile( 'button-nowiki' ), 'id' => 'mw-editbutton-nowiki', 'open' => "", 'close' => "", @@ -3632,7 +3625,6 @@ HTML 'tip' => wfMessage( 'nowiki_tip' )->text(), ), array( - 'image' => $wgLang->getImageFile( 'button-sig' ), 'id' => 'mw-editbutton-signature', 'open' => '--~~~~', 'close' => '', @@ -3640,7 +3632,6 @@ HTML 'tip' => wfMessage( 'sig_tip' )->text(), ), array( - 'image' => $wgLang->getImageFile( 'button-hr' ), 'id' => 'mw-editbutton-hr', 'open' => "\n----\n", 'close' => '', @@ -3656,7 +3647,8 @@ HTML } $params = array( - $wgStylePath . '/common/images/' . $tool['image'], + // Images are defined in ResourceLoaderEditToolbarModule + false, // Note that we use the tip both for the ALT tag and the TITLE tag of the image. // Older browsers show a "speedtip" type message only for ALT. // Ideally these should be different, realistically they diff --git a/includes/Export.php b/includes/Export.php index 43dfd17195..48a814d322 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -41,7 +41,7 @@ class WikiExporter { public $dumpUploadFileContents = false; /** @var string */ - protected $author_list = ""; + public $author_list = ""; const FULL = 1; const CURRENT = 2; @@ -56,13 +56,13 @@ class WikiExporter { const STUB = 1; /** @var int */ - protected $buffer; + public $buffer; /** @var int */ - protected $text; + public $text; /** @var DumpOutput */ - protected $sink; + public $sink; /** * Returns the export schema version. @@ -1370,10 +1370,10 @@ class DumpNotalkFilter extends DumpFilter { */ class DumpNamespaceFilter extends DumpFilter { /** @var bool */ - protected $invert = false; + public $invert = false; /** @var array */ - protected $namespaces = array(); + public $namespaces = array(); /** * @param DumpOutput $sink @@ -1437,13 +1437,13 @@ class DumpNamespaceFilter extends DumpFilter { * @ingroup Dump */ class DumpLatestFilter extends DumpFilter { - protected $page; + public $page; - protected $pageString; + public $pageString; - protected $rev; + public $rev; - protected $revString; + public $revString; /** * @param object $page diff --git a/includes/Feed.php b/includes/Feed.php index 03dd7f5c88..2fdfa424c4 100644 --- a/includes/Feed.php +++ b/includes/Feed.php @@ -37,19 +37,19 @@ */ class FeedItem { /** @var Title */ - protected $title; + public $title; - protected $description; + public $description; - protected $url; + public $url; - protected $date; + public $date; - protected $author; + public $author; - protected $uniqueId; + public $uniqueId; - protected $comments; + public $comments; public $rssIsPermalink = false; diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index ddea620b81..490df24690 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -1407,7 +1407,7 @@ function wfGetLangObj( $langcode = false ) { * * This function replaces all old wfMsg* functions. * - * @param string $key Message key + * @param string|string[] $key Message key, or array of keys * @param mixed $params,... Normal message parameters * @return Message * @@ -3179,10 +3179,10 @@ function wfDiff( $before, $after, $params = '-u' ) { // Kill the --- and +++ lines. They're not useful. $diff_lines = explode( "\n", $diff ); - if ( strpos( $diff_lines[0], '---' ) === 0 ) { + if ( isset( $diff_lines[0] ) && strpos( $diff_lines[0], '---' ) === 0 ) { unset( $diff_lines[0] ); } - if ( strpos( $diff_lines[1], '+++' ) === 0 ) { + if ( isset( $diff_lines[1] ) && strpos( $diff_lines[1], '+++' ) === 0 ) { unset( $diff_lines[1] ); } @@ -3366,7 +3366,10 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, ); if ( extension_loaded( 'gmp' ) && ( $engine == 'auto' || $engine == 'gmp' ) ) { - $result = gmp_strval( gmp_init( $input, $sourceBase ), $destBase ); + // Removing leading zeros works around broken base detection code in + // some PHP versions (see and + // ). + $result = gmp_strval( gmp_init( ltrim( $input, '0' ), $sourceBase ), $destBase ); } elseif ( extension_loaded( 'bcmath' ) && ( $engine == 'auto' || $engine == 'bcmath' ) ) { $decimal = '0'; foreach ( str_split( strtolower( $input ) ) as $char ) { diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php index d2be9e93b0..69f1120d43 100644 --- a/includes/HistoryBlob.php +++ b/includes/HistoryBlob.php @@ -351,10 +351,10 @@ class HistoryBlobCurStub { */ class DiffHistoryBlob implements HistoryBlob { /** @var array Uncompressed item cache */ - protected $mItems = array(); + public $mItems = array(); /** @var int Total uncompressed size */ - protected $mSize = 0; + public $mSize = 0; /** * @var array Array of diffs. If a diff D from A to B is notated D = B - A, @@ -364,20 +364,20 @@ class DiffHistoryBlob implements HistoryBlob { * diff[i] = { * { item[map[i]] - Z where i = 0 */ - protected $mDiffs; + public $mDiffs; /** @var array The diff map, see above */ - protected $mDiffMap; + public $mDiffMap; /** @var int The key for getText() */ - protected $mDefaultKey; + public $mDefaultKey; /** @var string Compressed storage */ public $mCompressed; /** @var bool True if the object is locked against further writes */ - protected $mFrozen = false; + public $mFrozen = false; /** * @var int The maximum uncompressed size before the object becomes sad diff --git a/includes/Html.php b/includes/Html.php index 48dbdba691..1e16e39430 100644 --- a/includes/Html.php +++ b/includes/Html.php @@ -937,20 +937,13 @@ class Html { * Get HTML for an info box with an icon. * * @param string $text Wikitext, get this with wfMessage()->plain() - * @param string $icon Icon name, file in skins/common/images + * @param string $icon Path to icon file (used as 'src' attribute) * @param string $alt Alternate text for the icon * @param string $class Additional class name to add to the wrapper div - * @param bool $useStylePath * * @return string */ - static function infoBox( $text, $icon, $alt, $class = false, $useStylePath = true ) { - global $wgStylePath; - - if ( $useStylePath ) { - $icon = $wgStylePath . '/common/images/' . $icon; - } - + static function infoBox( $text, $icon, $alt, $class = false ) { $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class" ) ); $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ) . diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php index 1eb8ca5294..8302124570 100644 --- a/includes/HttpFunctions.php +++ b/includes/HttpFunctions.php @@ -809,7 +809,8 @@ class CurlHttpRequest extends MWHttpRequest { return false; } - if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) { + $curlVersionInfo = curl_version(); + if ( $curlVersionInfo['version_number'] < 0x071304 ) { wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" ); return false; } diff --git a/includes/Import.php b/includes/Import.php index b3ca041635..5319076e82 100644 --- a/includes/Import.php +++ b/includes/Import.php @@ -891,7 +891,7 @@ class WikiImporter { /** This is a horrible hack used to keep source compatibility */ class UploadSourceAdapter { /** @var array */ - private static $sourceRegistrations = array(); + public static $sourceRegistrations = array(); /** @var string */ private $mSource; @@ -1015,13 +1015,13 @@ class UploadSourceAdapter { */ class WikiRevision { /** @todo Unused? */ - private $importer = null; + public $importer = null; /** @var Title */ public $title = null; /** @var int */ - private $id = 0; + public $id = 0; /** @var string */ public $timestamp = "20010115000000"; @@ -1035,10 +1035,10 @@ class WikiRevision { public $user_text = ""; /** @var string */ - protected $model = null; + public $model = null; /** @var string */ - protected $format = null; + public $format = null; /** @var string */ public $text = ""; @@ -1047,7 +1047,7 @@ class WikiRevision { protected $size; /** @var Content */ - protected $content = null; + public $content = null; /** @var ContentHandler */ protected $contentHandler = null; @@ -1056,31 +1056,31 @@ class WikiRevision { public $comment = ""; /** @var bool */ - protected $minor = false; + public $minor = false; /** @var string */ - protected $type = ""; + public $type = ""; /** @var string */ - protected $action = ""; + public $action = ""; /** @var string */ - protected $params = ""; + public $params = ""; /** @var string */ - protected $fileSrc = ''; + public $fileSrc = ''; /** @var bool|string */ - protected $sha1base36 = false; + public $sha1base36 = false; /** * @var bool * @todo Unused? */ - private $isTemp = false; + public $isTemp = false; /** @var string */ - protected $archiveName = ''; + public $archiveName = ''; protected $filename; @@ -1088,7 +1088,7 @@ class WikiRevision { protected $src; /** @todo Unused? */ - private $fileIsTemp; + public $fileIsTemp; /** @var bool */ private $mNoUpdates = false; diff --git a/includes/Linker.php b/includes/Linker.php index f0b16ab981..012bc1ba64 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -897,7 +897,7 @@ class Linker { */ public static function processResponsiveImages( $file, $thumb, $hp ) { global $wgResponsiveImages; - if ( $wgResponsiveImages ) { + if ( $wgResponsiveImages && $thumb && !$thumb->isError() ) { $hp15 = $hp; $hp15['width'] = round( $hp['width'] * 1.5 ); $hp20 = $hp; @@ -2151,7 +2151,7 @@ class Linker { return $tooltip; } - private static $accesskeycache; + public static $accesskeycache; /** * Given the id of an interface element, constructs the appropriate diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index 0424633a93..9213c021ef 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -27,7 +27,6 @@ */ class MediaWiki { /** - * @todo Fold $output, etc, into this * @var IContextSource */ private $context; @@ -37,30 +36,6 @@ class MediaWiki { */ private $config; - /** - * @param null|WebRequest $x - * @return WebRequest - */ - public function request( WebRequest $x = null ) { - $old = $this->context->getRequest(); - if ( $x ) { - $this->context->setRequest( $x ); - } - return $old; - } - - /** - * @param null|OutputPage $x - * @return OutputPage - */ - public function output( OutputPage $x = null ) { - $old = $this->context->getOutput(); - if ( $x ) { - $this->context->setOutput( $x ); - } - return $old; - } - /** * @param IContextSource|null $context */ diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index 8f0a2af230..bfd60111a5 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -172,6 +172,9 @@ class MimeMagic { */ private $mExtraInfo = ''; + /** @var Config */ + private $mConfig; + /** @var MimeMagic The singleton instance */ private static $instance = null; @@ -179,30 +182,40 @@ class MimeMagic { /** Initializes the MimeMagic object. This is called by MimeMagic::singleton(). * * This constructor parses the mime.types and mime.info files and build internal mappings. + * + * @todo Make this constructor private once everything uses the singleton instance + * @param Config $config */ - function __construct() { + function __construct( Config $config = null ) { + if ( !$config ) { + wfDebug( __METHOD__ . ' called with no Config instance passed to it' ); + $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); + } + $this->mConfig = $config; + /** * --- load mime.types --- */ - global $wgMimeTypeFile, $IP; + global $IP; # Allow media handling extensions adding MIME-types and MIME-info wfRunHooks( 'MimeMagicInit', array( $this ) ); $types = MM_WELL_KNOWN_MIME_TYPES; - if ( $wgMimeTypeFile == 'includes/mime.types' ) { - $wgMimeTypeFile = "$IP/$wgMimeTypeFile"; + $mimeTypeFile = $this->mConfig->get( 'MimeTypeFile' ); + if ( $mimeTypeFile == 'includes/mime.types' ) { + $mimeTypeFile = "$IP/$mimeTypeFile"; } - if ( $wgMimeTypeFile ) { - if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) { - wfDebug( __METHOD__ . ": loading mime types from $wgMimeTypeFile\n" ); + if ( $mimeTypeFile ) { + if ( is_file( $mimeTypeFile ) and is_readable( $mimeTypeFile ) ) { + wfDebug( __METHOD__ . ": loading mime types from $mimeTypeFile\n" ); $types .= "\n"; - $types .= file_get_contents( $wgMimeTypeFile ); + $types .= file_get_contents( $mimeTypeFile ); } else { - wfDebug( __METHOD__ . ": can't load mime types from $wgMimeTypeFile\n" ); + wfDebug( __METHOD__ . ": can't load mime types from $mimeTypeFile\n" ); } } else { wfDebug( __METHOD__ . ": no mime types file defined, using build-ins only.\n" ); @@ -266,20 +279,20 @@ class MimeMagic { * --- load mime.info --- */ - global $wgMimeInfoFile; - if ( $wgMimeInfoFile == 'includes/mime.info' ) { - $wgMimeInfoFile = "$IP/$wgMimeInfoFile"; + $mimeInfoFile = $this->mConfig->get( 'MimeInfoFile' ); + if ( $mimeInfoFile == 'includes/mime.info' ) { + $mimeInfoFile = "$IP/$mimeInfoFile"; } $info = MM_WELL_KNOWN_MIME_INFO; - if ( $wgMimeInfoFile ) { - if ( is_file( $wgMimeInfoFile ) and is_readable( $wgMimeInfoFile ) ) { - wfDebug( __METHOD__ . ": loading mime info from $wgMimeInfoFile\n" ); + if ( $mimeInfoFile ) { + if ( is_file( $mimeInfoFile ) and is_readable( $mimeInfoFile ) ) { + wfDebug( __METHOD__ . ": loading mime info from $mimeInfoFile\n" ); $info .= "\n"; - $info .= file_get_contents( $wgMimeInfoFile ); + $info .= file_get_contents( $mimeInfoFile ); } else { - wfDebug( __METHOD__ . ": can't load mime info from $wgMimeInfoFile\n" ); + wfDebug( __METHOD__ . ": can't load mime info from $mimeInfoFile\n" ); } } else { wfDebug( __METHOD__ . ": no mime info file defined, using build-ins only.\n" ); @@ -352,7 +365,9 @@ class MimeMagic { */ public static function singleton() { if ( self::$instance === null ) { - self::$instance = new MimeMagic; + self::$instance = new MimeMagic( + ConfigFactory::getDefaultInstance()->makeConfig( 'main' ) + ); } return self::$instance; } @@ -711,9 +726,9 @@ class MimeMagic { */ $xml = new XmlTypeCheck( $file ); if ( $xml->wellFormed ) { - global $wgXMLMimeTypes; - if ( isset( $wgXMLMimeTypes[$xml->getRootElement()] ) ) { - return $wgXMLMimeTypes[$xml->getRootElement()]; + $xmlMimeTypes = $this->mConfig->get( 'XMLMimeTypes' ); + if ( isset( $xmlMimeTypes[$xml->getRootElement()] ) ) { + return $xmlMimeTypes[$xml->getRootElement()]; } else { return 'application/xml'; } @@ -898,9 +913,9 @@ class MimeMagic { /** * Internal MIME type detection. Detection is done using an external * program, if $wgMimeDetectorCommand is set. Otherwise, the fileinfo - * extension and mime_content_type are tried (in this order), if they - * are available. If the detections fails and $ext is not false, the MIME - * type is guessed from the file extension, using guessTypesForExtension. + * extension is tried if it is available. If detection fails and $ext + * is not false, the MIME type is guessed from the file extension, + * using guessTypesForExtension. * * If the MIME type is still unknown, getimagesize is used to detect the * MIME type if the file is an image. If no MIME type can be determined, @@ -914,31 +929,19 @@ class MimeMagic { * @return string The MIME type of $file */ private function detectMimeType( $file, $ext = true ) { - global $wgMimeDetectorCommand; - /** @todo Make $ext default to false. Or better, remove it. */ if ( $ext ) { wfDebug( __METHOD__ . ": WARNING: use of the \$ext parameter is deprecated. " . "Use improveTypeFromExtension(\$mime, \$ext) instead.\n" ); } + $mimeDetectorCommand = $this->mConfig->get( 'MimeDetectorCommand' ); $m = null; - if ( $wgMimeDetectorCommand ) { + if ( $mimeDetectorCommand ) { $args = wfEscapeShellArg( $file ); - $m = wfShellExec( "$wgMimeDetectorCommand $args" ); + $m = wfShellExec( "$mimeDetectorCommand $args" ); } elseif ( function_exists( "finfo_open" ) && function_exists( "finfo_file" ) ) { - - # This required the fileinfo extension by PECL, - # see http://pecl.php.net/package/fileinfo - # This must be compiled into PHP - # - # finfo is the official replacement for the deprecated - # mime_content_type function, see below. - # - # If you may need to load the fileinfo extension at runtime, set - # $wgLoadFileinfoExtension in LocalSettings.php - - $mime_magic_resource = finfo_open( FILEINFO_MIME ); /* return MIME type ala mimetype extension */ + $mime_magic_resource = finfo_open( FILEINFO_MIME ); if ( $mime_magic_resource ) { $m = finfo_file( $mime_magic_resource, $file ); @@ -946,21 +949,6 @@ class MimeMagic { } else { wfDebug( __METHOD__ . ": finfo_open failed on " . FILEINFO_MIME . "!\n" ); } - } elseif ( function_exists( "mime_content_type" ) ) { - - # NOTE: this function is available since PHP 4.3.0, but only if - # PHP was compiled with --with-mime-magic or, before 4.3.2, with - # --enable-mime-magic. - # - # On Windows, you must set mime_magic.magicfile in php.ini to point - # to the mime.magic file bundled with PHP; sometimes, this may even - # be needed under *nix. - # - # Also note that this has been DEPRECATED in favor of the fileinfo - # extension by PECL, see above. - # See http://www.php.net/manual/en/ref.mime-magic.php for details. - - $m = mime_content_type( $file ); } else { wfDebug( __METHOD__ . ": no magic mime detector found!\n" ); } diff --git a/includes/MovePage.php b/includes/MovePage.php new file mode 100644 index 0000000000..fdece8d5be --- /dev/null +++ b/includes/MovePage.php @@ -0,0 +1,343 @@ +oldTitle = $oldTitle; + $this->newTitle = $newTitle; + } + + /** + * @param User $user + * @param string $reason + * @param bool $createRedirect + * @return Status + */ + public function move( User $user, $reason, $createRedirect ) { + global $wgCategoryCollation; + + // 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 ); + if ( $file->exists() ) { + $status = $file->move( $this->newTitle ); + if ( !$status->isOk() ) { + return $status; + } + } + // Clear RepoGroup process cache + RepoGroup::singleton()->clearCache( $this->oldTitle ); + RepoGroup::singleton()->clearCache( $this->newTitle ); # clear false negative cache + } + + $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own. + $pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE ); + $protected = $this->oldTitle->isProtected(); + + // Do the actual move + $this->moveToInternal( $user, $this->newTitle, $reason, $createRedirect ); + + // Refresh the sortkey for this row. Be careful to avoid resetting + // cl_timestamp, which may disturb time-based lists on some sites. + // @todo This block should be killed, it's duplicating code + // from LinksUpdate::getCategoryInsertions() and friends. + $prefixes = $dbw->select( + 'categorylinks', + array( 'cl_sortkey_prefix', 'cl_to' ), + array( 'cl_from' => $pageid ), + __METHOD__ + ); + if ( $this->newTitle->getNamespace() == NS_CATEGORY ) { + $type = 'subcat'; + } elseif ( $this->newTitle->getNamespace() == NS_FILE ) { + $type = 'file'; + } else { + $type = 'page'; + } + foreach ( $prefixes as $prefixRow ) { + $prefix = $prefixRow->cl_sortkey_prefix; + $catTo = $prefixRow->cl_to; + $dbw->update( 'categorylinks', + array( + 'cl_sortkey' => Collation::singleton()->getSortKey( + $this->newTitle->getCategorySortkey( $prefix ) ), + 'cl_collation' => $wgCategoryCollation, + 'cl_type' => $type, + 'cl_timestamp=cl_timestamp' ), + array( + 'cl_from' => $pageid, + 'cl_to' => $catTo ), + __METHOD__ + ); + } + + $redirid = $this->oldTitle->getArticleID(); + + if ( $protected ) { + # Protect the redirect title as the title used to be... + $dbw->insertSelect( 'page_restrictions', 'page_restrictions', + array( + 'pr_page' => $redirid, + 'pr_type' => 'pr_type', + 'pr_level' => 'pr_level', + 'pr_cascade' => 'pr_cascade', + 'pr_user' => 'pr_user', + 'pr_expiry' => 'pr_expiry' + ), + array( 'pr_page' => $pageid ), + __METHOD__, + array( 'IGNORE' ) + ); + # Update the protection log + $log = new LogPage( 'protect' ); + $comment = wfMessage( + 'prot_1movedto2', + $this->oldTitle->getPrefixedText(), + $this->newTitle->getPrefixedText() + )->inContentLanguage()->text(); + if ( $reason ) { + $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; + } + // @todo FIXME: $params? + $logId = $log->addEntry( + 'move_prot', + $this->newTitle, + $comment, + array( $this->oldTitle->getPrefixedText() ), + $user + ); + + // reread inserted pr_ids for log relation + $insertedPrIds = $dbw->select( + 'page_restrictions', + 'pr_id', + array( 'pr_page' => $redirid ), + __METHOD__ + ); + $logRelationsValues = array(); + foreach ( $insertedPrIds as $prid ) { + $logRelationsValues[] = $prid->pr_id; + } + $log->addRelations( 'pr_id', $logRelationsValues, $logId ); + } + + // Update *_from_namespace fields as needed + if ( $this->oldTitle->getNamespace() != $this->newTitle->getNamespace() ) { + $dbw->update( 'pagelinks', + array( 'pl_from_namespace' => $this->newTitle->getNamespace() ), + array( 'pl_from' => $pageid ), + __METHOD__ + ); + $dbw->update( 'templatelinks', + array( 'tl_from_namespace' => $this->newTitle->getNamespace() ), + array( 'tl_from' => $pageid ), + __METHOD__ + ); + $dbw->update( 'imagelinks', + array( 'il_from_namespace' => $this->newTitle->getNamespace() ), + array( 'il_from' => $pageid ), + __METHOD__ + ); + } + + # Update watchlists + $oldtitle = $this->oldTitle->getDBkey(); + $newtitle = $this->newTitle->getDBkey(); + $oldsnamespace = MWNamespace::getSubject( $this->oldTitle->getNamespace() ); + $newsnamespace = MWNamespace::getSubject( $this->newTitle->getNamespace() ); + if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) { + WatchedItem::duplicateEntries( $this->oldTitle, $this->newTitle ); + } + + $dbw->commit( __METHOD__ ); + + wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) ); + return Status::newGood(); + + } + + /** + * Move page to a title which is either a redirect to the + * source page or nonexistent + * + * @fixme This was basically directly moved from Title, it should be split into smaller functions + * @param User $user the User doing the move + * @param Title $nt The page to move to, which should be a redirect or nonexistent + * @param string $reason The reason for the move + * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check + * if the user has the suppressredirect right + * @throws MWException + */ + private function moveToInternal( User $user, &$nt, $reason = '', $createRedirect = true ) { + global $wgContLang; + + if ( $nt->exists() ) { + $moveOverRedirect = true; + $logType = 'move_redir'; + } else { + $moveOverRedirect = false; + $logType = 'move'; + } + + if ( $createRedirect ) { + if ( $this->oldTitle->getNamespace() == NS_CATEGORY + && !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled() + ) { + $redirectContent = new WikitextContent( + wfMessage( 'category-move-redirect-override' ) + ->params( $nt->getPrefixedText() )->inContentLanguage()->plain() ); + } else { + $contentHandler = ContentHandler::getForTitle( $this->oldTitle ); + $redirectContent = $contentHandler->makeRedirectContent( $nt, + wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() ); + } + + // NOTE: If this page's content model does not support redirects, $redirectContent will be null. + } else { + $redirectContent = null; + } + + // bug 57084: log_page should be the ID of the *moved* page + $oldid = $this->oldTitle->getArticleID(); + $logTitle = clone $this->oldTitle; + + $logEntry = new ManualLogEntry( 'move', $logType ); + $logEntry->setPerformer( $user ); + $logEntry->setTarget( $logTitle ); + $logEntry->setComment( $reason ); + $logEntry->setParameters( array( + '4::target' => $nt->getPrefixedText(), + '5::noredir' => $redirectContent ? '0': '1', + ) ); + + $formatter = LogFormatter::newFromEntry( $logEntry ); + $formatter->setContext( RequestContext::newExtraneousContext( $this->oldTitle ) ); + $comment = $formatter->getPlainActionText(); + if ( $reason ) { + $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; + } + # Truncate for whole multibyte characters. + $comment = $wgContLang->truncate( $comment, 255 ); + + $dbw = wfGetDB( DB_MASTER ); + + $newpage = WikiPage::factory( $nt ); + + if ( $moveOverRedirect ) { + $newid = $nt->getArticleID(); + $newcontent = $newpage->getContent(); + + # Delete the old redirect. We don't save it to history since + # by definition if we've got here it's rather uninteresting. + # We have to remove it so that the next step doesn't trigger + # a conflict on the unique namespace+title index... + $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ ); + + $newpage->doDeleteUpdates( $newid, $newcontent ); + } + + # Save a null revision in the page's history notifying of the move + $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user ); + if ( !is_object( $nullRevision ) ) { + throw new MWException( 'No valid null revision produced in ' . __METHOD__ ); + } + + $nullRevision->insertOn( $dbw ); + + # Change the name of the target page: + $dbw->update( 'page', + /* SET */ array( + 'page_namespace' => $nt->getNamespace(), + 'page_title' => $nt->getDBkey(), + ), + /* WHERE */ array( 'page_id' => $oldid ), + __METHOD__ + ); + + // clean up the old title before reset article id - bug 45348 + if ( !$redirectContent ) { + WikiPage::onArticleDelete( $this->oldTitle ); + } + + $this->oldTitle->resetArticleID( 0 ); // 0 == non existing + $nt->resetArticleID( $oldid ); + $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397 + + $newpage->updateRevisionOn( $dbw, $nullRevision ); + + wfRunHooks( 'NewRevisionFromEditComplete', + array( $newpage, $nullRevision, $nullRevision->getParentId(), $user ) ); + + $newpage->doEditUpdates( $nullRevision, $user, array( 'changed' => false ) ); + + if ( !$moveOverRedirect ) { + WikiPage::onArticleCreate( $nt ); + } + + # Recreate the redirect, this time in the other direction. + if ( $redirectContent ) { + $redirectArticle = WikiPage::factory( $this->oldTitle ); + $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397 + $newid = $redirectArticle->insertOn( $dbw ); + if ( $newid ) { // sanity + $this->oldTitle->resetArticleID( $newid ); + $redirectRevision = new Revision( array( + 'title' => $this->oldTitle, // for determining the default content model + 'page' => $newid, + 'user_text' => $user->getName(), + 'user' => $user->getId(), + 'comment' => $comment, + 'content' => $redirectContent ) ); + $redirectRevision->insertOn( $dbw ); + $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 ); + + wfRunHooks( 'NewRevisionFromEditComplete', + array( $redirectArticle, $redirectRevision, false, $user ) ); + + $redirectArticle->doEditUpdates( $redirectRevision, $user, array( 'created' => true ) ); + } + } + + # Log the move + $logid = $logEntry->insert(); + $logEntry->publish( $logid ); + } + +} \ No newline at end of file diff --git a/includes/OutputPage.php b/includes/OutputPage.php index a58a79a200..22a601222a 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -2769,7 +2769,10 @@ $templates ); $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); - // Extract modules that know they're empty + + // Extract modules that know they're empty and see if we have one or more + // raw modules + $isRaw = false; foreach ( $grpModules as $key => $module ) { // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857) // If we're only getting the styles, we don't need to do anything for empty modules. @@ -2779,6 +2782,8 @@ $templates $links['states'][$key] = 'ready'; } } + + $isRaw |= $module->isRaw(); } // If there are no non-empty modules, skip this group @@ -2797,7 +2802,9 @@ $templates ); } else { $links['html'] .= Html::inlineScript( - $resourceLoader->makeModuleResponse( $context, $grpModules ) + ResourceLoader::makeLoaderConditionalScript( + $resourceLoader->makeModuleResponse( $context, $grpModules ) + ) ); } $links['html'] .= "\n"; @@ -2843,6 +2850,17 @@ $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. + $link = Html::inlineScript( + ResourceLoader::makeLoaderConditionalScript( + Xml::encodeJsCall( 'document.write', array( $link ) ) + ) + ); + } // For modules requested directly in the html via or + ' +' + ), + array( + // Don't condition wrap raw modules (like the startup module) + array( 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ), + ' ' ), // Load module styles only @@ -151,18 +159,22 @@ class OutputPageTest extends MediaWikiTestCase { ' ' ), - // Load private module (scripts) + // Load private module (only=scripts) array( array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ), - ' + ' ' ), // Load private module (combined) array( array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), - ' + ' ' ), // Load module script with with ESI @@ -175,6 +187,24 @@ class OutputPageTest extends MediaWikiTestCase { array( array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), ' +', + ), + // Load no modules + array( + array( array(), ResourceLoaderModule::TYPE_COMBINED ), + '', + ), + // noscript group + array( + array( 'test.noscript', ResourceLoaderModule::TYPE_STYLES ), + ' +' + ), + // Load two modules in separate groups + array( + array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ), + ' + ', ), ); @@ -218,6 +248,22 @@ class OutputPageTest extends MediaWikiTestCase { 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }', 'group' => 'private', )), + 'test.raw' => new ResourceLoaderTestModule( array( + 'script' => 'mw.test.baz( { token: 123 } );', + 'isRaw' => true, + )), + 'test.noscript' => new ResourceLoaderTestModule( array( + 'styles' => '.mw-test-noscript { content: "style"; }', + 'group' => 'noscript', + )), + 'test.group.bar' => new ResourceLoaderTestModule( array( + 'styles' => '.mw-group-bar { content: "style"; }', + 'group' => 'bar', + )), + 'test.group.foo' => new ResourceLoaderTestModule( array( + 'styles' => '.mw-group-foo { content: "style"; }', + 'group' => 'foo', + )), ) ); $links = $method->invokeArgs( $out, $args ); // Strip comments to avoid variation due to wgDBname in WikiID and cache key diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index b2b0d34bc0..97f61466fb 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -39,84 +39,61 @@ class TitleTest extends MediaWikiTestCase { } } - /** - * See also mediawiki.Title.test.js - * @covers Title::secureAndSplit - * @todo This method should be split into 2 separate tests each with a provider - * @note This mainly tests MediaWikiTitleCodec::parseTitle(). - */ - public function testSecureAndSplit() { - $this->setMwGlobals( array( - 'wgLocalInterwikis' => array( 'localtestiw' ), - 'wgHooks' => array( - 'InterwikiLoadPrefix' => array( - function ( $prefix, &$data ) { - if ( $prefix === 'localtestiw' ) { - $data = array( 'iw_url' => 'localtestiw' ); - } elseif ( $prefix === 'remotetestiw' ) { - $data = array( 'iw_url' => 'remotetestiw' ); - } - return false; - } - ) - ) - )); - // Valid - foreach ( array( - 'Sandbox', - 'A "B"', - 'A \'B\'', - '.com', - '~', - '#', - '"', - '\'', - 'Talk:Sandbox', - 'Talk:Foo:Sandbox', - 'File:Example.svg', - 'File_talk:Example.svg', - 'Foo/.../Sandbox', - 'Sandbox/...', - 'A~~', - ':A', + public function provideValidSecureAndSplit() { + return array( + array( 'Sandbox' ), + array( 'A "B"' ), + array( 'A \'B\'' ), + array( '.com' ), + array( '~' ), + array( '#' ), + array( '"' ), + array( '\'' ), + array( 'Talk:Sandbox' ), + array( 'Talk:Foo:Sandbox' ), + array( 'File:Example.svg' ), + array( 'File_talk:Example.svg' ), + array( 'Foo/.../Sandbox' ), + array( 'Sandbox/...' ), + array( 'A~~' ), + array( ':A' ), // Length is 256 total, but only title part matters - 'Category:' . str_repeat( 'x', 248 ), - str_repeat( 'x', 252 ), + array( 'Category:' . str_repeat( 'x', 248 ) ), + array( str_repeat( 'x', 252 ) ), // interwiki prefix - 'localtestiw: #anchor', - 'localtestiw:', - 'localtestiw:foo', - 'localtestiw: foo # anchor', - 'localtestiw: Talk: Sandbox # anchor', - 'remotetestiw:', - 'remotetestiw: Talk: # anchor', - 'remotetestiw: #bar', - 'remotetestiw: Talk:', - 'remotetestiw: Talk: Foo', - 'localtestiw:remotetestiw:', - 'localtestiw:remotetestiw:foo' - ) as $text ) { - $this->assertInstanceOf( 'Title', Title::newFromText( $text ), "Valid: $text" ); - } + array( 'localtestiw: #anchor' ), + array( 'localtestiw:' ), + array( 'localtestiw:foo' ), + array( 'localtestiw: foo # anchor' ), + array( 'localtestiw: Talk: Sandbox # anchor' ), + array( 'remotetestiw:' ), + array( 'remotetestiw: Talk: # anchor' ), + array( 'remotetestiw: #bar' ), + array( 'remotetestiw: Talk:' ), + array( 'remotetestiw: Talk: Foo' ), + array( 'localtestiw:remotetestiw:' ), + array( 'localtestiw:remotetestiw:foo' ) + ); + } - // Invalid - foreach ( array( - '', - ':', - '__ __', - ' __ ', + public function provideInvalidSecureAndSplit() { + return array( + array( '' ), + array( ':' ), + array( '__ __' ), + array( ' __ ' ), // Bad characters forbidden regardless of wgLegalTitleChars - 'A [ B', - 'A ] B', - 'A { B', - 'A } B', - 'A < B', - 'A > B', - 'A | B', + array( 'A [ B' ), + array( 'A ] B' ), + array( 'A { B' ), + array( 'A } B' ), + array( 'A < B' ), + array( 'A > B' ), + array( 'A | B' ), // URL encoding - 'A%20B', - 'A%23B', - 'A%2523B', + array( 'A%20B' ), + array( 'A%23B' ), + array( 'A%2523B' ), // XML/HTML character entity references // Note: Commented out because they are not marked invalid by the PHP test as // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first. @@ -124,32 +101,70 @@ class TitleTest extends MediaWikiTestCase { //'A é B', //'A é B', // Subject of NS_TALK does not roundtrip to NS_MAIN - 'Talk:File:Example.svg', + array( 'Talk:File:Example.svg' ), // Directory navigation - '.', - '..', - './Sandbox', - '../Sandbox', - 'Foo/./Sandbox', - 'Foo/../Sandbox', - 'Sandbox/.', - 'Sandbox/..', + array( '.' ), + array( '..' ), + array( './Sandbox' ), + array( '../Sandbox' ), + array( 'Foo/./Sandbox' ), + array( 'Foo/../Sandbox' ), + array( 'Sandbox/.' ), + array( 'Sandbox/..' ), // Tilde - 'A ~~~ Name', - 'A ~~~~ Signature', - 'A ~~~~~ Timestamp', - str_repeat( 'x', 256 ), + array( 'A ~~~ Name' ), + array( 'A ~~~~ Signature' ), + array( 'A ~~~~~ Timestamp' ), + array( str_repeat( 'x', 256 ) ), // Namespace prefix without actual title - 'Talk:', - 'Talk:#', - 'Category: ', - 'Category: #bar', + array( 'Talk:' ), + array( 'Talk:#' ), + array( 'Category: ' ), + array( 'Category: #bar' ), // interwiki prefix - 'localtestiw: Talk: # anchor', - 'localtestiw: Talk:' - ) as $text ) { - $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); - } + array( 'localtestiw: Talk: # anchor' ), + array( 'localtestiw: Talk:' ) + ); + } + + private function secureAndSplitGlobals() { + $this->setMwGlobals( array( + 'wgLocalInterwikis' => array( 'localtestiw' ), + 'wgHooks' => array( + 'InterwikiLoadPrefix' => array( + function ( $prefix, &$data ) { + if ( $prefix === 'localtestiw' ) { + $data = array( 'iw_url' => 'localtestiw' ); + } elseif ( $prefix === 'remotetestiw' ) { + $data = array( 'iw_url' => 'remotetestiw' ); + } + return false; + } + ) + ) + )); + } + + /** + * See also mediawiki.Title.test.js + * @covers Title::secureAndSplit + * @dataProvider provideValidSecureAndSplit + * @note This mainly tests MediaWikiTitleCodec::parseTitle(). + */ + public function testSecureAndSplitValid( $text ) { + $this->secureAndSplitGlobals(); + $this->assertInstanceOf( 'Title', Title::newFromText( $text ), "Valid: $text" ); + } + + /** + * See also mediawiki.Title.test.js + * @covers Title::secureAndSplit + * @dataProvider provideInvalidSecureAndSplit + * @note This mainly tests MediaWikiTitleCodec::parseTitle(). + */ + public function testSecureAndSplitInvalid( $text ) { + $this->secureAndSplitGlobals(); + $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); } public static function provideConvertByteClassToUnicodeClass() { diff --git a/tests/phpunit/includes/UserMailerTest.php b/tests/phpunit/includes/UserMailerTest.php deleted file mode 100644 index dca8aeb997..0000000000 --- a/tests/phpunit/includes/UserMailerTest.php +++ /dev/null @@ -1,14 +0,0 @@ -assertEquals( - "=?UTF-8?Q?=C4=88u=20legebla=3F?=", - UserMailer::quotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); - } - -} diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index 36de114ead..cabbf10fc0 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -323,4 +323,35 @@ class UserTest extends MediaWikiTestCase { $this->assertFalse( $user->checkPasswordValidity( 'Passpass' )->isGood() ); $this->assertEquals( 'password-login-forbidden', $user->getPasswordValidity( 'Passpass' ) ); } + + /** + * @covers User::getCanonicalName() + * @dataProvider provideGetCanonicalName + */ + public function testGetCanonicalName( $name, $expectedArray, $msg ) { + foreach ( $expectedArray as $validate => $expected ) { + $this->assertEquals( + User::getCanonicalName( $name, $validate === 'false' ? false : $validate ), + $expected, + $msg . ' (' . $validate . ')' + ); + } + } + + public function provideGetCanonicalName() { + return array( + array( ' trailing space ', array( 'creatable' => 'Trailing space' ), 'Trailing spaces' ), + // @todo FIXME: Maybe the createable name should be 'Talk:Username' or false to reject? + array( 'Talk:Username', array( 'creatable' => 'Username', 'usable' => 'Username', + 'valid' => 'Username', 'false' => 'Talk:Username' ), 'Namespace prefix' ), + array( ' name with # hash', array( 'creatable' => false, 'usable' => false ), 'With hash' ), + array( 'Multi spaces', array( 'creatable' => 'Multi spaces', + 'usable' => 'Multi spaces' ), 'Multi spaces' ), + array( 'lowercase', array( 'creatable' => 'Lowercase' ), 'Lowercase' ), + array( 'in[]valid', array( 'creatable' => false, 'usable' => false, 'valid' => false, + 'false' => 'In[]valid' ), 'Invalid' ), + array( 'with / slash', array( 'creatable' => false, 'usable' => false, 'valid' => false, + 'false' => 'With / slash' ), 'With slash' ), + ); + } } diff --git a/tests/phpunit/includes/actions/ActionTest.php b/tests/phpunit/includes/actions/ActionTest.php index eb370d9204..cc6fb11a6f 100644 --- a/tests/phpunit/includes/actions/ActionTest.php +++ b/tests/phpunit/includes/actions/ActionTest.php @@ -7,6 +7,7 @@ * @author Thiemo Mättig * * @group Action + * @group Database */ class ActionTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/api/ApiModuleManagerTest.php b/tests/phpunit/includes/api/ApiModuleManagerTest.php index 201eed188c..dab81e162a 100644 --- a/tests/phpunit/includes/api/ApiModuleManagerTest.php +++ b/tests/phpunit/includes/api/ApiModuleManagerTest.php @@ -149,6 +149,7 @@ class ApiModuleManagerTest extends MediaWikiTestCase { } /** + * @covers ApiModuleManager::getModule * @dataProvider getModuleProvider */ public function testGetModule( $modules, $name, $expectedClass ) { @@ -172,6 +173,9 @@ class ApiModuleManagerTest extends MediaWikiTestCase { $this->assertNotSame( $module1, $module4 ); } + /** + * @covers ApiModuleManager::getModule + */ public function testGetModule_null() { $modules = array( 'login' => 'ApiLogin', @@ -185,6 +189,9 @@ class ApiModuleManagerTest extends MediaWikiTestCase { $this->assertNull( $moduleManager->getModule( 'login', 'bla' ), 'wrong group' ); } + /** + * @covers ApiModuleManager::getNames + */ public function testGetNames() { $fooModules = array( 'login' => 'ApiLogin', @@ -208,6 +215,9 @@ class ApiModuleManagerTest extends MediaWikiTestCase { $this->assertArrayEquals( array_keys( $allModules ), $allNames ); } + /** + * @covers ApiModuleManager::getNamesWithClasses + */ public function testGetNamesWithClasses() { $fooModules = array( 'login' => 'ApiLogin', @@ -234,6 +244,9 @@ class ApiModuleManagerTest extends MediaWikiTestCase { $this->assertArrayEquals( $allModules, $allNamesWithClasses ); } + /** + * @covers ApiModuleManager::getModuleGroup + */ public function testGetModuleGroup() { $fooModules = array( 'login' => 'ApiLogin', @@ -254,6 +267,9 @@ class ApiModuleManagerTest extends MediaWikiTestCase { $this->assertNull( $moduleManager->getModuleGroup( 'quux' ) ); } + /** + * @covers ApiModuleManager::getGroups + */ public function testGetGroups() { $fooModules = array( 'login' => 'ApiLogin', @@ -273,4 +289,30 @@ class ApiModuleManagerTest extends MediaWikiTestCase { $this->assertArrayEquals( array( 'foo', 'bar' ), $groups ); } + /** + * @covers ApiModuleManager::getClassName + */ + public function testGetClassName() { + $fooModules = array( + 'login' => 'ApiLogin', + 'logout' => 'ApiLogout', + ); + + $barModules = array( + 'feedcontributions' => array( 'class' => 'ApiFeedContributions' ), + 'feedrecentchanges' => array( 'class' => 'ApiFeedRecentChanges' ), + ); + + $moduleManager = $this->getModuleManager(); + $moduleManager->addModules( $fooModules, 'foo' ); + $moduleManager->addModules( $barModules, 'bar' ); + + $this->assertEquals( 'ApiLogin', $moduleManager->getClassName( 'login' ) ); + $this->assertEquals( 'ApiLogout', $moduleManager->getClassName( 'logout' ) ); + $this->assertEquals( 'ApiFeedContributions', $moduleManager->getClassName( 'feedcontributions' ) ); + $this->assertEquals( 'ApiFeedRecentChanges', $moduleManager->getClassName( 'feedrecentchanges' ) ); + $this->assertFalse( $moduleManager->getClassName( 'nonexistentmodule' ) ); + } + + } diff --git a/tests/phpunit/includes/cache/LocalisationCacheTest.php b/tests/phpunit/includes/cache/LocalisationCacheTest.php new file mode 100644 index 0000000000..fc06a50126 --- /dev/null +++ b/tests/phpunit/includes/cache/LocalisationCacheTest.php @@ -0,0 +1,91 @@ +setMwGlobals( array( + 'wgMessagesDirs' => array( "$IP/tests/phpunit/data/localisationcache" ), + 'wgExtensionMessagesFiles' => array(), + 'wgHooks' => array(), + ) ); + } + + public function testPuralRulesFallback() { + $cache = new LocalisationCache( array( 'store' => 'detect' ) ); + + $this->assertEquals( + $cache->getItem( 'ar', 'pluralRules' ), + $cache->getItem( 'arz', 'pluralRules' ), + 'arz plural rules (undefined) fallback to ar (defined)' + ); + + $this->assertEquals( + $cache->getItem( 'ar', 'compiledPluralRules' ), + $cache->getItem( 'arz', 'compiledPluralRules' ), + 'arz compiled plural rules (undefined) fallback to ar (defined)' + ); + + $this->assertNotEquals( + $cache->getItem( 'ksh', 'pluralRules' ), + $cache->getItem( 'de', 'pluralRules' ), + 'ksh plural rules (defined) dont fallback to de (defined)' + ); + + $this->assertNotEquals( + $cache->getItem( 'ksh', 'compiledPluralRules' ), + $cache->getItem( 'de', 'compiledPluralRules' ), + 'ksh compiled plural rules (defined) dont fallback to de (defined)' + ); + } + + public function testRecacheFallbacks() { + $lc = new LocalisationCache( array( 'store' => 'detect' ) ); + $lc->recache( 'uk' ); + $this->assertEquals( + array( + 'present-uk' => 'uk', + 'present-ru' => 'ru', + 'present-en' => 'en', + ), + $lc->getItem( 'uk', 'messages' ), + 'Fallbacks are only used to fill missing data' + ); + } + + public function testRecacheFallbacksWithHooks() { + global $wgHooks; + + // Use hook to provide updates for messages. This is what the + // LocalisationUpdate extension does. See bug 68781. + $wgHooks['LocalisationCacheRecacheFallback'][] = function ( + LocalisationCache $lc, + $code, + array &$cache + ) { + if ( $code === 'ru' ) { + $cache['messages']['present-uk'] = 'ru-override'; + $cache['messages']['present-ru'] = 'ru-override'; + $cache['messages']['present-en'] = 'ru-override'; + } + }; + + $lc = new LocalisationCache( array( 'store' => 'detect' ) ); + $lc->recache( 'uk' ); + $this->assertEquals( + array( + 'present-uk' => 'uk', + 'present-ru' => 'ru-override', + 'present-en' => 'ru-override', + ), + $lc->getItem( 'uk', 'messages' ), + 'Updates provided by hooks follow the normal fallback order.' + ); + } +} diff --git a/tests/phpunit/includes/cache/RedisBloomCacheTest.php b/tests/phpunit/includes/cache/RedisBloomCacheTest.php new file mode 100644 index 0000000000..3d491e9062 --- /dev/null +++ b/tests/phpunit/includes/cache/RedisBloomCacheTest.php @@ -0,0 +1,71 @@ +delete( "unit-testing-" . self::$suffix ); + } else { + $this->markTestSkipped( 'The main bloom cache is not redis.' ); + } + } + + public function testBloomCache() { + $key = "unit-testing-" . self::$suffix; + $fcache = BloomCache::get( 'main' ); + $count = 1500; + + $this->assertTrue( $fcache->delete( $key ), "OK delete of filter '$key'." ); + $this->assertTrue( $fcache->init( $key, $count, .001 ), "OK init of filter '$key'." ); + + $members = array(); + for ( $i = 0; $i < $count; ++$i ) { + $members[] = "$i-value-$i"; + } + $this->assertTrue( $fcache->add( $key, $members ), "Addition of members to '$key' OK." ); + + for ( $i = 0; $i < $count; ++$i ) { + $this->assertTrue( $fcache->isHit( $key, "$i-value-$i" ), "Hit on member '$i-value-$i'." ); + } + + $falsePositives = array(); + for ( $i = $count; $i < 2 * $count; ++$i ) { + if ( $fcache->isHit( $key, "value$i" ) ) { + $falsePositives[] = "value$i"; + } + } + + $eFalsePositives = array( + 'value1763', + 'value2245', + 'value2353', + 'value2791', + 'value2898', + 'value2975' + ); + $this->assertEquals( $eFalsePositives, $falsePositives, "Correct number of false positives found." ); + } + + protected function tearDown() { + parent::tearDown(); + + $fcache = BloomCache::get( 'main' ); + if ( $fcache instanceof BloomCacheRedis ) { + $fcache->delete( "unit-testing-" . self::$suffix ); + } + } +} diff --git a/tests/phpunit/includes/changes/OldChangesListTest.php b/tests/phpunit/includes/changes/OldChangesListTest.php index d009192b02..3a36b9f302 100644 --- a/tests/phpunit/includes/changes/OldChangesListTest.php +++ b/tests/phpunit/includes/changes/OldChangesListTest.php @@ -123,6 +123,17 @@ class OldChangesListTest extends MediaWikiLangTestCase { ); } + public function testRecentChangesLine_Tags() { + $recentChange = $this->getEditChange(); + $recentChange->mAttribs['ts_tags'] = 'vandalism,newbie'; + + $oldChangesList = $this->getOldChangesList(); + $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 ); + + $this->assertRegExp( '/
  • /', $line ); + $this->assertRegExp( '/
  • /', $line ); + } + private function getNewBotEditChange() { $user = $this->getTestUser(); diff --git a/tests/phpunit/includes/config/GlobalVarConfigTest.php b/tests/phpunit/includes/config/GlobalVarConfigTest.php index a99908155a..b15ffa7210 100644 --- a/tests/phpunit/includes/config/GlobalVarConfigTest.php +++ b/tests/phpunit/includes/config/GlobalVarConfigTest.php @@ -97,6 +97,7 @@ class GlobalVarConfigTest extends MediaWikiTestCase { * @covers GlobalVarConfig::setWithPrefix */ public function testSet( $name, $prefix, $var ) { + $this->hideDeprecated( 'GlobalVarConfig::set' ); $this->maybeStashGlobal( $var ); $config = new GlobalVarConfig( $prefix ); $random = wfRandomString(); diff --git a/tests/phpunit/includes/content/JSONContentTest.php b/tests/phpunit/includes/content/JSONContentTest.php deleted file mode 100644 index acfdc0e59f..0000000000 --- a/tests/phpunit/includes/content/JSONContentTest.php +++ /dev/null @@ -1,115 +0,0 @@ -assertEquals( $isValid, $obj->isValid() ); - $this->assertEquals( $expected, $obj->getJsonData() ); - } - - public function provideValidConstruction() { - return array( - array( 'foo', CONTENT_MODEL_JSON, false, null ), - array( FormatJson::encode( array() ), CONTENT_MODEL_JSON, true, array() ), - array( FormatJson::encode( array( 'foo' ) ), CONTENT_MODEL_JSON, true, array( 'foo' ) ), - ); - } - - /** - * @dataProvider provideDataToEncode - */ - public function testBeautifyUsesFormatJson( $data ) { - $obj = new JSONContent( FormatJson::encode( $data ) ); - $this->assertEquals( FormatJson::encode( $data, true ), $obj->beautifyJSON() ); - } - - public function provideDataToEncode() { - return array( - array( array() ), - array( array( 'foo' ) ), - array( array( 'foo', 'bar' ) ), - array( array( 'baz' => 'foo', 'bar' ) ), - array( array( 'baz' => 1000, 'bar' ) ), - ); - } - - /** - * @dataProvider provideDataToEncode - */ - public function testPreSaveTransform( $data ) { - $obj = new JSONContent( FormatJson::encode( $data ) ); - $newObj = $obj->preSaveTransform( $this->getMockTitle(), $this->getMockUser(), $this->getMockParserOptions() ); - $this->assertTrue( $newObj->equals( new JSONContent( FormatJson::encode( $data, true ) ) ) ); - } - - private function getMockTitle() { - return $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); - } - - private function getMockUser() { - return $this->getMockBuilder( 'User' ) - ->disableOriginalConstructor() - ->getMock(); - } - private function getMockParserOptions() { - return $this->getMockBuilder( 'ParserOptions' ) - ->disableOriginalConstructor() - ->getMock(); - } - - /** - * @dataProvider provideDataAndParserText - */ - public function testFillParserOutput( $data, $expected ) { - $obj = new JSONContent( FormatJson::encode( $data ) ); - $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true ); - $this->assertInstanceOf( 'ParserOutput', $parserOutput ); -// var_dump( $parserOutput->getText(), "\n" ); - $this->assertEquals( $expected, $parserOutput->getText() ); - } - - public function provideDataAndParserText() { - return array( - array( - array(), - '
    ' - ), - array( - array( 'foo' ), - '
    0"foo"
    ' - ), - array( - array( 'foo', 'bar' ), - '' . - "\n" . - '
    0"foo"
    1"bar"
    ' - ), - array( - array( 'baz' => 'foo', 'bar' ), - '' . - "\n" . - '
    baz"foo"
    0"bar"
    ' - ), - array( - array( 'baz' => 1000, 'bar' ), - '' . - "\n" . - '
    baz1000
    0"bar"
    ' - ), - array( - array( ''), - '
    0"<script>alert("evil!")</script>"
    ', - ), - ); - } -} diff --git a/tests/phpunit/includes/content/JsonContentTest.php b/tests/phpunit/includes/content/JsonContentTest.php new file mode 100644 index 0000000000..6c77d1aaf0 --- /dev/null +++ b/tests/phpunit/includes/content/JsonContentTest.php @@ -0,0 +1,115 @@ +assertEquals( $isValid, $obj->isValid() ); + $this->assertEquals( $expected, $obj->getJsonData() ); + } + + public function provideValidConstruction() { + return array( + array( 'foo', CONTENT_MODEL_JSON, false, null ), + array( FormatJson::encode( array() ), CONTENT_MODEL_JSON, true, array() ), + array( FormatJson::encode( array( 'foo' ) ), CONTENT_MODEL_JSON, true, array( 'foo' ) ), + ); + } + + /** + * @dataProvider provideDataToEncode + */ + public function testBeautifyUsesFormatJson( $data ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $this->assertEquals( FormatJson::encode( $data, true ), $obj->beautifyJSON() ); + } + + public function provideDataToEncode() { + return array( + array( array() ), + array( array( 'foo' ) ), + array( array( 'foo', 'bar' ) ), + array( array( 'baz' => 'foo', 'bar' ) ), + array( array( 'baz' => 1000, 'bar' ) ), + ); + } + + /** + * @dataProvider provideDataToEncode + */ + public function testPreSaveTransform( $data ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $newObj = $obj->preSaveTransform( $this->getMockTitle(), $this->getMockUser(), $this->getMockParserOptions() ); + $this->assertTrue( $newObj->equals( new JsonContent( FormatJson::encode( $data, true ) ) ) ); + } + + private function getMockTitle() { + return $this->getMockBuilder( 'Title' ) + ->disableOriginalConstructor() + ->getMock(); + } + + private function getMockUser() { + return $this->getMockBuilder( 'User' ) + ->disableOriginalConstructor() + ->getMock(); + } + private function getMockParserOptions() { + return $this->getMockBuilder( 'ParserOptions' ) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @dataProvider provideDataAndParserText + */ + public function testFillParserOutput( $data, $expected ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true ); + $this->assertInstanceOf( 'ParserOutput', $parserOutput ); +// var_dump( $parserOutput->getText(), "\n" ); + $this->assertEquals( $expected, $parserOutput->getText() ); + } + + public function provideDataAndParserText() { + return array( + array( + array(), + '
    ' + ), + array( + array( 'foo' ), + '
    0"foo"
    ' + ), + array( + array( 'foo', 'bar' ), + '' . + "\n" . + '
    0"foo"
    1"bar"
    ' + ), + array( + array( 'baz' => 'foo', 'bar' ), + '' . + "\n" . + '
    baz"foo"
    0"bar"
    ' + ), + array( + array( 'baz' => 1000, 'bar' ), + '' . + "\n" . + '
    baz1000
    0"bar"
    ' + ), + array( + array( ''), + '
    0"<script>alert("evil!")</script>"
    ', + ), + ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index 1db6faec53..98b4ca046c 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -162,6 +162,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $this->assertEquals( "DROP INDEX foo -- dropping index", $this->replaceVars( "DROP INDEX /*i*/foo ON /*_*/bar -- dropping index" ) ); + $this->assertEquals( "INSERT OR IGNORE INTO foo VALUES ('bar')", + $this->replaceVars( "INSERT OR IGNORE INTO foo VALUES ('bar')" ) + ); } /** diff --git a/tests/phpunit/includes/mail/MailAddressTest.php b/tests/phpunit/includes/mail/MailAddressTest.php new file mode 100644 index 0000000000..2d0781204f --- /dev/null +++ b/tests/phpunit/includes/mail/MailAddressTest.php @@ -0,0 +1,63 @@ +assertInstanceOf( 'MailAddress', $ma ); + } + + /** + * @covers MailAddress::newFromUser + */ + public function testNewFromUser() { + $user = $this->getMock( 'User' ); + $user->expects( $this->any() )->method( 'getName' )->will( $this->returnValue( 'UserName' ) ); + $user->expects( $this->any() )->method( 'getEmail' )->will( $this->returnValue( 'foo@bar.baz' ) ); + $user->expects( $this->any() )->method( 'getRealName' )->will( $this->returnValue( 'Real name' ) ); + + $ma = MailAddress::newFromUser( $user ); + $this->assertInstanceOf( 'MailAddress', $ma ); + $this->setMwGlobals( 'wgEnotifUseRealName', true ); + $this->assertEquals( 'Real name ', $ma->toString() ); + $this->setMwGlobals( 'wgEnotifUseRealName', false ); + $this->assertEquals( 'UserName ', $ma->toString() ); + } + + /** + * @covers MailAddress::toString + * @dataProvider provideToString + */ + public function testToString( $useRealName, $address, $name, $realName, $expected ) { + if ( wfIsWindows() ) { + $this->markTestSkipped( 'This test only works on non-Windows platforms' ); + } + $this->setMwGlobals( 'wgEnotifUseRealName', $useRealName ); + $ma = new MailAddress( $address, $name, $realName ); + $this->assertEquals( $expected, $ma->toString() ); + } + + public static function provideToString() { + return array( + array( true, 'foo@bar.baz', 'FooBar', 'Foo Bar', 'Foo Bar ' ), + array( true, 'foo@bar.baz', 'UserName', null, 'UserName ' ), + array( true, 'foo@bar.baz', 'AUser', 'My real name', 'My real name ' ), + array( true, 'foo@bar.baz', 'A.user.name', 'my@real.name', '"my@real.name" ' ), + array( false, 'foo@bar.baz', 'AUserName', 'Some real name', 'AUserName ' ), + array( false, 'foo@bar.baz', '', '', 'foo@bar.baz' ), + array( true, 'foo@bar.baz', '', '', 'foo@bar.baz' ), + ); + } + + /** + * @covers MailAddress::__toString + */ + public function test__ToString() { + $ma = new MailAddress( 'some@email.com', 'UserName', 'A real name' ); + $this->assertEquals( $ma->toString(), (string)$ma ); + } + +} \ No newline at end of file diff --git a/tests/phpunit/includes/mail/UserMailerTest.php b/tests/phpunit/includes/mail/UserMailerTest.php new file mode 100644 index 0000000000..dca8aeb997 --- /dev/null +++ b/tests/phpunit/includes/mail/UserMailerTest.php @@ -0,0 +1,14 @@ +assertEquals( + "=?UTF-8?Q?=C4=88u=20legebla=3F?=", + UserMailer::quotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); + } + +} diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php index 247e352c65..f0bd42a078 100644 --- a/tests/phpunit/includes/media/ExifRotationTest.php +++ b/tests/phpunit/includes/media/ExifRotationTest.php @@ -32,7 +32,7 @@ class ExifRotationTest extends MediaWikiMediaTestCase { * @dataProvider provideFiles */ public function testMetadata( $name, $type, $info ) { - if ( !BitmapHandler::canRotate() ) { + if ( !$this->handler->canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } $file = $this->dataFile( $name, $type ); @@ -40,12 +40,29 @@ class ExifRotationTest extends MediaWikiMediaTestCase { $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); } + /** + * Same as before, but with auto-rotation set to auto. + * + * This sets scaler to image magick, which we should detect as + * supporting rotation. + * @dataProvider provideFiles + */ + public function testMetadataAutoRotate( $name, $type, $info ) { + $this->setMwGlobals( 'wgEnableAutoRotation', null ); + $this->setMwGlobals( 'wgUseImageMagick', true ); + $this->setMwGlobals( 'wgUseImageResize', true ); + + $file = $this->dataFile( $name, $type ); + $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" ); + $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); + } + /** * * @dataProvider provideFiles */ public function testRotationRendering( $name, $type, $info, $thumbs ) { - if ( !BitmapHandler::canRotate() ) { + if ( !$this->handler->canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } foreach ( $thumbs as $size => $out ) { @@ -133,6 +150,19 @@ class ExifRotationTest extends MediaWikiMediaTestCase { $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); } + /** + * Same as before, but with auto-rotation set to auto and an image scaler that doesn't support it. + * @dataProvider provideFilesNoAutoRotate + */ + public function testMetadataAutoRotateUnsupported( $name, $type, $info ) { + $this->setMwGlobals( 'wgEnableAutoRotation', null ); + $this->setMwGlobals( 'wgUseImageResize', false ); + + $file = $this->dataFile( $name, $type ); + $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" ); + $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); + } + /** * * @dataProvider provideFilesNoAutoRotate diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php index 160ddad020..987b6e6467 100644 --- a/tests/phpunit/includes/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -23,9 +23,6 @@ class BagOStuffTest extends MediaWikiTestCase { $this->cache->delete( wfMemcKey( 'test' ) ); } - protected function tearDown() { - } - public function testMerge() { $key = wfMemcKey( 'test' ); diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index a450972334..df891f5a4b 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -83,14 +83,28 @@ class MediaWikiParserTest { . implode( ' ', $filesToTest ) ); $suite = new PHPUnit_Framework_TestSuite; + $testList = array(); + $counter = 0; foreach ( $filesToTest as $fileName ) { - $testsName = basename( $fileName, '.txt' ); + // Call the highest level directory the extension name. + // It may or may not actually be, but it should be close + // enough to cause there to be separate names for different + // things, which is good enough for our purposes. + $extensionName = basename( dirname( $fileName ) ); + $testsName = $extensionName . '⁄' . basename( $fileName, '.txt' ); $escapedFileName = strtr( $fileName, array( "'" => "\\'", '\\' => '\\\\' ) ); - /* This used to be ucfirst( basename( dirname( $filename ) ) ) - * and then was ucfirst( basename( $filename, '.txt' ) - * but that didn't work with names like foo.tests.txt - */ - $parserTestClassName = str_replace( '.', '_', ucfirst( $testsName ) ); + $parserTestClassName = ucfirst( $testsName ); + // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php + // Prepend 'ParserTest_' to be paranoid about it not starting with a number + $parserTestClassName = 'ParserTest_' . preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName ); + if ( isset( $testList[$parserTestClassName] ) ) { + // If a conflict happens, gives a very unclear fatal. + // So as a last ditch effort to prevent that eventuality, if there + // is a conflict, append a number. + $counter++; + $parserTestClassName .= $counter; + } + $testList[$parserTestClassName] = true; $parserTestClassDefinition = <<getSections(), 'getSections() with proper value when

    is used' ); } + + /** + * @dataProvider provideNormalizeLinkUrl + * @covers Parser::normalizeLinkUrl + * @covers Parser::normalizeUrlComponent + */ + public function testNormalizeLinkUrl( $explanation, $url, $expected ) { + $this->assertEquals( $expected, Parser::normalizeLinkUrl( $url ), $explanation ); + } + + public static function provideNormalizeLinkUrl() { + return array( + array( + 'Escaping of unsafe characters', + 'http://example.org/foo bar?param[]="value"¶m[]=valüe', + 'http://example.org/foo%20bar?param%5B%5D=%22value%22¶m%5B%5D=val%C3%BCe', + ), + array( + 'Case normalization of percent-encoded characters', + 'http://example.org/%ab%cD%Ef%FF', + 'http://example.org/%AB%CD%EF%FF', + ), + array( + 'Unescaping of safe characters', + 'http://example.org/%3C%66%6f%6F%3E?%3C%66%6f%6F%3E#%3C%66%6f%6F%3E', + 'http://example.org/%3Cfoo%3E?%3Cfoo%3E#%3Cfoo%3E', + ), + array( + 'Context-sensitive replacement of sometimes-safe characters', + 'http://example.org/%23%2F%3F%26%3D%2B%3B?%23%2F%3F%26%3D%2B%3B#%23%2F%3F%26%3D%2B%3B', + 'http://example.org/%23%2F%3F&=+;?%23/?%26%3D%2B%3B#%23/?&=+;', + ), + ); + } + // @todo Add tests for cleanSig() / cleanSigInSig(), getSection(), // replaceSection(), getPreloadText() } diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php index 0c250bd762..a189387328 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderStartupModuleTest.php @@ -9,10 +9,7 @@ class ResourceLoaderStartupModuleTest extends ResourceLoaderTestCase { 'modules' => array(), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - } + "local": "/w/load.php" } );mw.loader.register( [] );' ) ), array( array( @@ -22,10 +19,7 @@ mw.loader.addSource( { ), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - } + "local": "/w/load.php" } );mw.loader.register( [ [ "test.blank", @@ -42,10 +36,7 @@ mw.loader.addSource( { ), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - } + "local": "/w/load.php" } );mw.loader.register( [ [ "test.blank", @@ -73,10 +64,7 @@ mw.loader.addSource( { ), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - } + "local": "/w/load.php" } );mw.loader.register( [ [ "test.blank", @@ -97,14 +85,8 @@ mw.loader.addSource( { ), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - }, - "example": { - "loadScript": "http://example.org/w/load.php", - "apiScript": "http://example.org/w/api.php" - } + "local": "/w/load.php", + "example": "http://example.org/w/load.php" } );mw.loader.register( [ [ "test.blank", @@ -140,10 +122,7 @@ mw.loader.addSource( { ), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - } + "local": "/w/load.php" } );mw.loader.register( [ [ "test.x.core", @@ -238,14 +217,8 @@ mw.loader.addSource( { ), 'out' => ' mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - }, - "example": { - "loadScript": "http://example.org/w/load.php", - "apiScript": "http://example.org/w/api.php" - } + "local": "/w/load.php", + "example": "http://example.org/w/load.php" } );mw.loader.register( [ [ "test.blank", @@ -369,7 +342,7 @@ mw.loader.addSource( { $rl->register( $modules ); $module = new ResourceLoaderStartUpModule(); $this->assertEquals( -'mw.loader.addSource({"local":{"loadScript":"/w/load.php","apiScript":"/w/api.php"}});' +'mw.loader.addSource({"local":"/w/load.php"});' . 'mw.loader.register([' . '["test.blank","1388534400"],' . '["test.min","1388534400",["test.blank"],null,"local",' @@ -390,10 +363,7 @@ mw.loader.addSource( { $module = new ResourceLoaderStartUpModule(); $this->assertEquals( 'mw.loader.addSource( { - "local": { - "loadScript": "/w/load.php", - "apiScript": "/w/api.php" - } + "local": "/w/load.php" } );mw.loader.register( [ [ "test.blank", diff --git a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php index d72c5e7cde..d2e118c543 100644 --- a/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php +++ b/tests/phpunit/includes/resourceloader/ResourceLoaderTest.php @@ -161,6 +161,43 @@ class ResourceLoaderTest extends ResourceLoaderTestCase { ); } + public static function provideAddSource() { + return array( + array( 'examplewiki', '//example.org/w/load.php', 'examplewiki' ), + array( 'example2wiki', array( 'loadScript' => '//example.com/w/load.php' ), 'example2wiki' ), + array( + array( 'foowiki' => '//foo.org/w/load.php', 'bazwiki' => '//baz.org/w/load.php' ), + null, + array( 'foowiki', 'bazwiki' ) + ), + array( + array( 'foowiki' => '//foo.org/w/load.php' ), + null, + false, + ), + ); + } + + /** + * @dataProvider provideAddSource + * @covers ResourceLoader::addSource + */ + public function testAddSource( $name, $info, $expected ) { + $rl = new ResourceLoader; + if ( $expected === false ) { + $this->setExpectedException( 'MWException', 'ResourceLoader duplicate source addition error' ); + $rl->addSource( $name, $info ); + } + $rl->addSource( $name, $info ); + if ( is_array( $expected ) ) { + foreach ( $expected as $source ) { + $this->assertArrayHasKey( $source, $rl->getSources() ); + } + } else { + $this->assertArrayHasKey( $expected, $rl->getSources() ); + } + } + public static function fakeSources() { return array( 'examplewiki' => array( diff --git a/tests/phpunit/includes/specials/ImageListPagerTest.php b/tests/phpunit/includes/specials/ImageListPagerTest.php index 8a0ac970b7..22bdefdff3 100644 --- a/tests/phpunit/includes/specials/ImageListPagerTest.php +++ b/tests/phpunit/includes/specials/ImageListPagerTest.php @@ -6,6 +6,7 @@ * Copyright © 2013, Siebrand Mazeland * Copyright © 2013, Wikimedia Foundation Inc. * + * @group Database */ class ImageListPagerTest extends MediaWikiTestCase { diff --git a/tests/phpunit/includes/specials/SpecialMIMESearchTest.php b/tests/phpunit/includes/specials/SpecialMIMESearchTest.php index bd952811ca..14d196851a 100644 --- a/tests/phpunit/includes/specials/SpecialMIMESearchTest.php +++ b/tests/phpunit/includes/specials/SpecialMIMESearchTest.php @@ -1,4 +1,8 @@ user->addGroup( 'sysop' ); $data = $this->doApiRequest( array( 'action' => 'upload', - 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'url' => 'http://upload.wikimedia.org/wikipedia/mediawiki/b/bc/Wiki.png', 'asyncdownload' => 1, 'filename' => 'UploadFromUrlTest.png', 'token' => $token, @@ -182,7 +182,7 @@ class UploadFromUrlTest extends ApiTestCase { $data = $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', - 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'url' => 'http://upload.wikimedia.org/wikipedia/mediawiki/b/bc/Wiki.png', 'ignorewarnings' => true, 'token' => $token, ), $data ); @@ -213,7 +213,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', - 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'url' => 'http://upload.wikimedia.org/wikipedia/mediawiki/b/bc/Wiki.png', 'asyncdownload' => 1, 'token' => $token, 'leavemessage' => 1, @@ -234,7 +234,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', - 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'url' => 'http://upload.wikimedia.org/wikipedia/mediawiki/b/bc/Wiki.png', 'asyncdownload' => 1, 'token' => $token, 'leavemessage' => 1, @@ -279,7 +279,7 @@ class UploadFromUrlTest extends ApiTestCase { $params = array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', - 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', + 'url' => 'http://upload.wikimedia.org/wikipedia/mediawiki/b/bc/Wiki.png', 'asyncdownload' => 1, 'token' => $token, ); diff --git a/tests/phpunit/maintenance/MaintenanceTest.php b/tests/phpunit/maintenance/MaintenanceTest.php index a13f7bf9a4..e2fc82474e 100644 --- a/tests/phpunit/maintenance/MaintenanceTest.php +++ b/tests/phpunit/maintenance/MaintenanceTest.php @@ -810,4 +810,21 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->simulateShutdown(); $this->assertOutputPrePostShutdown( "foobar\n\n", false ); } + + /** + * @covers Maintenance::getConfig + */ + public function testGetConfig() { + $this->assertInstanceOf( 'Config', $this->m->getConfig() ); + $this->assertSame( ConfigFactory::getDefaultInstance()->makeConfig( 'main' ), $this->m->getConfig() ); + } + + /** + * @covers Maintenance::setConfig + */ + public function testSetConfig() { + $conf = $this->getMock( 'Config' ); + $this->m->setConfig( $conf ); + $this->assertSame( $conf, $this->m->getConfig() ); + } } diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js index 1005316e74..e8c5121429 100644 --- a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js @@ -41,7 +41,7 @@ // Add two characters using scary black magic spanText = $span.text(); d = findDivergenceIndex( origText, spanText ); - spanTextNew = spanText.substr( 0, d ) + origText[d] + origText[d] + '...'; + spanTextNew = spanText.slice( 0, d ) + origText[d] + origText[d] + '...'; assert.gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index d43baeeb1f..16f90df8cf 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -465,4 +465,11 @@ grammarTest( langCode, test ); } } ); + + QUnit.test( 'List to text test', 4, function ( assert ) { + assert.equal( mw.language.listToText( [] ), '', 'Blank list' ); + assert.equal( mw.language.listToText( ['a'] ), 'a', 'Single item' ); + assert.equal( mw.language.listToText( ['a', 'b'] ), 'a and b', 'Two items' ); + assert.equal( mw.language.listToText( ['a', 'b', 'c'] ), 'a, b and c', 'More than two items' ); + } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index 96813305da..7e0ee917f3 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -32,9 +32,7 @@ mw.loader.addSource( 'testloader', - { - loadScript: QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' ) - } + QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' ) ); QUnit.test( 'Initial check', 8, function ( assert ) { @@ -809,7 +807,7 @@ // Confirm that mw.loader.load() works with protocol-relative URLs target = target.replace( /https?:/, '' ); - assert.equal( target.substr( 0, 2 ), '//', + assert.equal( target.slice( 0, 2 ), '//', 'URL must be relative to test relative URLs!' ); diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js index 7dec626507..ed03418a9c 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -21,8 +21,7 @@ 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 OPR/15.0.1147.153', 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36 OPR/16.0.1196.62', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36 OPR/23.0.1522.75', - // Internet Explorer 7+ - 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)', + // Internet Explorer 8+ 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', @@ -43,12 +42,13 @@ 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ], gradeC: [ - // Internet Explorer < 7 + // Internet Explorer < 8 'Mozilla/2.0 (compatible; MSIE 3.03; Windows 3.1)', 'Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)', 'Mozilla/4.0 (compatible; MSIE 5.0; Windows 98;)', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 'Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)', + 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)', // Firefox < 3 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.2) Gecko/20060308 Firefox/1.5.0.2', 'Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1.1) Gecko/20070311 Firefox/2.0.0.1',