From: jenkins-bot Date: Thu, 10 Jul 2014 07:24:50 +0000 (+0000) Subject: Merge "Remove unused message 'postcomment'" X-Git-Tag: 1.31.0-rc.0~15018 X-Git-Url: http://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/operations/recherche.php?a=commitdiff_plain;h=cb0486fe0cb8bebd95fec3fd1f173fe46bc038db;hp=9cae4a506c5fbe05884742aa1415f777c6205113;p=lhc%2Fweb%2Fwiklou.git Merge "Remove unused message 'postcomment'" --- diff --git a/.travis.yml b/.travis.yml index 64075f1da2..dedb4e14a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,7 @@ script: - php tests/phpunit/phpunit.php notifications: + email: false irc: channels: - "chat.freenode.net#mediawiki-core" diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index 8974e68c5b..70f6084b6f 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -1,5 +1,5 @@ -Security reminder: MediaWiki does not require PHP's register_globals. If you -have it on, turn it '''off''' if you can. +Security reminder: If you have PHP's register_globals option set, you must +turn it off. MediaWiki will no longer work with it enabled. == MediaWiki 1.24 == @@ -9,6 +9,9 @@ MediaWiki 1.24 is an alpha-quality branch and is not recommended for use in production. === Configuration changes in 1.24 === +* MediaWiki will no longer run if register_globals is enabled. It has been + deprecated for 5 years now, and was removed in PHP 5.4. For more information + about why, see . * The server's canonical hostname is available as $wgServerName, which is exposed in both mw.config and ApiQuerySiteInfo. * Introduced $wgPagePropsHaveSortkey as a backwards-compatibility switch, @@ -26,6 +29,10 @@ production. prefixes (i.e. turned into interlanguage links when $wgInterwikiMagic is set to true). * $wgParserTestRemote has been removed. +* $wgCountTotalSearchHits has been removed. If you're concerned about efficiency + of search, you should use something like CirrusSearch instead of built in + search. +* Users in the 'sysop' group have access to Special:MergeHistory by default. === New features in 1.24 === * Added a new hook, "WhatLinksHereProps", to allow extensions to annotate @@ -91,6 +98,17 @@ production. uses to find extension license information. * Browser tests are now included to verify basic wiki functionality in developer environments. For details on running tests, see tests/browser/README.mediawiki. +* Upgrade jStorage to v0.4.10. +* {{!}} is now a magic word that produces the | character. This removes the need + for Template:! for purposes such as passing pipes inside of parameters. +* (bug 20790) The block log snippet on Special:Contributions and while + editing user and user talk pages now works for IP range blocks. +* (bug 9360) Added ability to change the page language for MediaWiki pages using + 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.7.0. +* (bug 67042) Added support for the HTML5 tag for East Asian typography. === Bug fixes in 1.24 === * (bug 49116) Footer copyright notice is now always displayed in user language @@ -114,6 +132,9 @@ production. however delete the redirect page. * (bug 22683) {{msgnw:}} and other uses of PPFrame::RECOVER_ORIG will correctly recover the original code of extension tags. +* (bug 65757) MSSQL: Update script drops unnamed constraints to be prepared + for future updates. Because it's doing so heuristically, it may fail or drop + wrong constraints. === Web API changes in 1.24 === * action=parse API now supports prop=modules, which provides the list of @@ -134,6 +155,12 @@ production. The old format is still used if prop isn't provided, but this is deprecated. * meta=userinfo can now return the count of unread pages on the watchlist. * list=watchlist can now filter by unread status. +* The deprecated action=parse&prop=languageshtml has been removed. +* (bug 48071) action=setnotificationtimestamp no longer throws PHP or database + errors when no pages are given. +* (bug 60734) Actions that use ApiPageSet (e.g. purge, watch, + setnotificationtimestamp) will now include continuation information when + using a generator. === Languages updated in 1.24 === @@ -183,6 +210,24 @@ changes to languages because of Bugzilla reports. * The "jquery.json" module has been deprecated. Use the "json" module instead. * Removed HTMLForm::addJS(). (deprecated since 1.18) * Removed LogEventsList::showHeader(). (deprecated since 1.19) +* Removed ImageGalleryBase::useSkin(). (deprecated since 1.18) +* Removed DatabaseMysqlBase::getLagFromProcesslist(). (deprecated since 1.19) +* Removed LoadBalancer::closeConnecton(). (deprecated since 1.18) +* Removed ApiBase::createContext(). (deprecated since 1.19) +* BREAKING CHANGE: The undocumented Special{$this->getName()}BeforeFormDisplay + set of hooks has been removed and replaced by a single new hook + SpecialPageBeforeFormDisplay. +* (bug 65781) Removed block warning on included {{Special:Contributions}} +* Removed Skin::makeGlobalVariablesScript. (deprecated since 1.19) +* Removed MWNamespace::isMain(). (deprecated since 1.19) +* Removed Preferences::loadOldSearchNs(). (deprecated since 1.19) +* Removed OutputPage::getStatusMessage(). (deprecated since 1.18) +* Removed OutputPage::isUserJsAllowed(). (deprecated since 1.18) +* Removed Title::updateTitleProtection(). (deprecated since 1.19) +* Removed ParserOptions::setSkin(). (deprecated since 1.19) +* Removed Title::escapeCanonicalURL(). (deprecated since 1.19) +* Removed Title::escapeLocalURL(). (deprecated since 1.19) +* Removed Title::escapeFullURL(). (deprecated since 1.19) ==== Renamed classes ==== * CLDRPluralRuleConverter_Expression to CLDRPluralRuleConverterExpression @@ -225,6 +270,7 @@ changes to languages because of Bugzilla reports. ==== Removed classes ==== * IPBlockForm - Use SpecialBlock directly * WatchlistEditor - Use SpecialEditWatchlist directly +* FormatExif - Use FormatMetadata directly == Compatibility == diff --git a/StartProfiler.sample b/StartProfiler.sample index db5e0ff9e2..d9b5288411 100644 --- a/StartProfiler.sample +++ b/StartProfiler.sample @@ -2,13 +2,22 @@ /** * To use a profiler, copy this file to StartProfiler.php, - * and add something like this: + * and add either: * - * $wgProfiler['class'] = 'Profiler'; + * // Does not support the debugging toolbar + * // Stores profiling information in the database + * // Requires running maintenance/archives/patch-profiling.sql + * $wgProfiler['class'] = 'ProfilerSimpleDB' + * + * or: + * + * // Supports the debugging toolbar + * // Does not store profiling information in the database + * $wgProfiler['class'] = 'ProfilerStandard'; * * Or for a sampling profiler: * if ( !mt_rand( 0, 100 ) ) { - * $wgProfiler['class'] = 'Profiler'; + * $wgProfiler['class'] = 'ProfilerSimpleDB'; * } else { * $wgProfiler['class'] = 'ProfilerStub'; * } diff --git a/docs/hooks.txt b/docs/hooks.txt index 8d7e654ac2..ab23bc0073 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -326,6 +326,13 @@ $revCount: Number of revisions in the XML file $sRevCount: Number of successfully imported revisions $pageInfo: associative array of page information +'AfterParserFetchFileAndTitle': After an image gallery is formed by Parser, +just before adding its HTML to parser output. +$parser: Parser object that called the hook +$ig: Gallery, an object of one of the gallery classes (inheriting from +ImageGalleryBase) +$html: HTML generated by the gallery + 'AjaxAddScript': Called in output page just before the initialisation of the javascript ajax engine. The hook is only called when ajax is enabled ( $wgUseAjax = true; ). @@ -1496,7 +1503,7 @@ $result: Change this value to override the result of wfIsTrustedProxy() $url: URL used to upload from &$allowed: Boolean indicating if uploading is allowed for given URL -'isValidEmailAddr': Override the result of User::isValidEmailAddr(), for +'isValidEmailAddr': Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization. $addr: The e-mail address entered by the user &$result: Set this and return false to override the internal checks @@ -1705,8 +1712,8 @@ $refreshLinks: RefreshLinks object 'MagicWordwgVariableIDs': When defining new magic words IDs. $variableIDs: array of strings -'MakeGlobalVariablesScript': Called right before Skin::makeVariablesScript is -executed. Ideally, this hook should only be used to add variables that depend on +'MakeGlobalVariablesScript': Called at end of OutputPage::getJSVars. +Ideally, this hook should only be used to add variables that depend on the current page/request; static configuration should be added through ResourceLoaderGetConfigVars instead. &$vars: variable (or multiple variables) to be added into the output of @@ -1750,6 +1757,30 @@ caches. $title: name of the page changed. $text: new contents of the page. +'MimeMagicInit': Before processing the list mapping MIME types to media types +and the list mapping MIME types to file extensions. +As an extension author, you are encouraged to submit patches to MediaWiki's +core to add new MIME types to mime.types. +$mimeMagic: Instance of MimeMagic. + Use $mimeMagic->addExtraInfo( $stringOfInfo ); + for adding new MIME info to the list. + Use $mimeMagic->addExtraTypes( $stringOfTypes ); + for adding new MIME types to the list. + +'MimeMagicImproveFromExtension': Allows MW extensions to further improve the +MIME type detected by considering the file extension. +$mimeMagic: Instance of MimeMagic. +$ext: File extension. +&$mime: MIME type (in/out). + +'MimeMagicGuessFromContent': Allows MW extensions guess the MIME by content. +$mimeMagic: Instance of MimeMagic. +&$head: First 1024 bytes of the file in a string (in - Do not alter!). +&$tail: More or equal than last 65558 bytes of the file in a string + (in - Do not alter!). +$file: File path. +&$mime: MIME type (out). + 'ModifyExportQuery': Modify the query used by the exporter. $db: The database object to be queried. &$tables: Tables in the query. @@ -2354,6 +2385,10 @@ software. $software: The array of software in format 'name' => 'version'. See SpecialVersion::softwareInformation(). +'SpecialPageBeforeFormDisplay': Before executing the HTMLForm object. +$name: name of the special page +&$form: HTMLForm object + 'SpecialBlockModifyFormFields': Add more fields to Special:Block $sp: SpecialPage object, for context &$fields: Current HTMLForm fields diff --git a/docs/scripts.txt b/docs/scripts.txt index 4f167093ec..c6fa674cec 100644 --- a/docs/scripts.txt +++ b/docs/scripts.txt @@ -40,7 +40,7 @@ Primary scripts: To enable the profileinfo.php itself, you'll need to set $wgDBadminuser and $wgDBadminpassword in your LocalSettings.php, as well as $wgEnableProfileInfo - See also https://www.mediawiki.org/wiki/How_to_debug#Profiling. + See also https://www.mediawiki.org/wiki/Manual:Profiling . thumb.php Script used to resize images if it is configured to be done when the web diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 3ac4722e27..67f9a1c098 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -110,7 +110,6 @@ $wgAutoloadLocalClasses = array( 'ICacheHelper' => 'includes/CacheHelper.php', 'IcuCollation' => 'includes/Collation.php', 'IdentityCollation' => 'includes/Collation.php', - 'ImageQueryPage' => 'includes/ImageQueryPage.php', 'ImportStreamSource' => 'includes/Import.php', 'ImportStringSource' => 'includes/Import.php', 'IndexPager' => 'includes/Pager.php', @@ -140,7 +139,6 @@ $wgAutoloadLocalClasses = array( 'MWInit' => 'includes/Init.php', 'MWNamespace' => 'includes/Namespace.php', 'OutputPage' => 'includes/OutputPage.php', - 'PageQueryPage' => 'includes/PageQueryPage.php', 'Pager' => 'includes/Pager.php', 'PasswordError' => 'includes/User.php', 'PathRouter' => 'includes/PathRouter.php', @@ -156,7 +154,6 @@ $wgAutoloadLocalClasses = array( 'PreferencesForm' => 'includes/Preferences.php', 'PrefixSearch' => 'includes/PrefixSearch.php', 'ProtectionForm' => 'includes/ProtectionForm.php', - 'QueryPage' => 'includes/QueryPage.php', 'QuickTemplate' => 'includes/SkinTemplate.php', 'RawMessage' => 'includes/Message.php', 'ReverseChronologicalPager' => 'includes/Pager.php', @@ -196,7 +193,6 @@ $wgAutoloadLocalClasses = array( 'UserCache' => 'includes/cache/UserCache.php', 'UserMailer' => 'includes/UserMailer.php', 'UserRightsProxy' => 'includes/UserRightsProxy.php', - 'WantedQueryPage' => 'includes/QueryPage.php', 'WatchedItem' => 'includes/WatchedItem.php', 'WebRequest' => 'includes/WebRequest.php', 'WebRequestUpload' => 'includes/WebRequest.php', @@ -717,6 +713,7 @@ $wgAutoloadLocalClasses = array( 'ManualLogEntry' => 'includes/logging/LogEntry.php', 'MoveLogFormatter' => 'includes/logging/MoveLogFormatter.php', 'NewUsersLogFormatter' => 'includes/logging/NewUsersLogFormatter.php', + 'PageLangLogFormatter' => 'includes/logging/PageLangLogFormatter.php', 'PatrolLog' => 'includes/logging/PatrolLog.php', 'PatrolLogFormatter' => 'includes/logging/PatrolLogFormatter.php', 'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php', @@ -741,7 +738,6 @@ $wgAutoloadLocalClasses = array( 'DjVuImage' => 'includes/media/DjVuImage.php', 'Exif' => 'includes/media/Exif.php', 'ExifBitmapHandler' => 'includes/media/ExifBitmap.php', - 'FormatExif' => 'includes/media/FormatMetadata.php', 'FormatMetadata' => 'includes/media/FormatMetadata.php', 'GIFHandler' => 'includes/media/GIF.php', 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php', @@ -927,13 +923,17 @@ $wgAutoloadLocalClasses = array( # includes/specialpage 'ChangesListSpecialPage' => 'includes/specialpage/ChangesListSpecialPage.php', 'FormSpecialPage' => 'includes/specialpage/FormSpecialPage.php', + 'ImageQueryPage' => 'includes/specialpage/ImageQueryPage.php', 'IncludableSpecialPage' => 'includes/specialpage/IncludableSpecialPage.php', + 'PageQueryPage' => 'includes/specialpage/PageQueryPage.php', + 'QueryPage' => 'includes/specialpage/QueryPage.php', 'RedirectSpecialArticle' => 'includes/specialpage/RedirectSpecialPage.php', 'RedirectSpecialPage' => 'includes/specialpage/RedirectSpecialPage.php', 'SpecialPage' => 'includes/specialpage/SpecialPage.php', 'SpecialPageFactory' => 'includes/specialpage/SpecialPageFactory.php', 'SpecialRedirectToSpecial' => 'includes/specialpage/RedirectSpecialPage.php', 'UnlistedSpecialPage' => 'includes/specialpage/UnlistedSpecialPage.php', + 'WantedQueryPage' => 'includes/specialpage/WantedQueryPage.php', # includes/specials 'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php', @@ -1017,6 +1017,7 @@ $wgAutoloadLocalClasses = array( 'SpecialMyuploads' => 'includes/specials/SpecialMyRedirectPages.php', 'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php', 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php', + 'SpecialPageLanguage' => 'includes/specials/SpecialPageLanguage.php', 'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php', 'SpecialPagesWithProp' => 'includes/specials/SpecialPagesWithProp.php', 'SpecialPermanentLink' => 'includes/specials/SpecialPermanentLink.php', diff --git a/includes/Block.php b/includes/Block.php index 3896369ce4..c393a795d4 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -1368,7 +1368,7 @@ class Block { $this->getId(), $lang->formatExpiry( $this->mExpiry ), (string)$intended, - $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ), + $lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ), ); } } diff --git a/includes/Category.php b/includes/Category.php index e235e4e94e..7bab464bf7 100644 --- a/includes/Category.php +++ b/includes/Category.php @@ -320,7 +320,7 @@ class Category { wfProfileIn( __METHOD__ ); $dbw = wfGetDB( DB_MASTER ); - $dbw->begin( __METHOD__ ); + $dbw->startAtomic( __METHOD__ ); # Insert the row if it doesn't exist yet (e.g., this is being run via # update.php from a pre-1.16 schema). TODO: This will cause lots and @@ -360,7 +360,7 @@ class Category { array( 'cat_title' => $this->mName ), __METHOD__ ); - $dbw->commit( __METHOD__ ); + $dbw->endAtomic( __METHOD__ ); wfProfileOut( __METHOD__ ); diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index 49818e6a12..cd9eaa9c10 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -707,7 +707,10 @@ class CategoryViewer extends ContextSource { // to refresh the incorrect category table entry -- which should be // quick due to the small number of entries. $totalcnt = $rescnt; - $this->cat->refreshCounts(); + $category = $this->cat; + wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $category ) { + $category->refreshCounts(); + } ); } else { // Case 3: hopeless. Don't give a total count at all. // Messages: category-subcat-count-limited, category-article-count-limited, diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php index 28db8a1bdb..f51a5a8023 100644 --- a/includes/ChangeTags.php +++ b/includes/ChangeTags.php @@ -77,9 +77,9 @@ class ChangeTags { * Add tags to a change given its rc_id, rev_id and/or log_id * * @param string|array $tags Tags to add to the change - * @param int $rc_id rc_id of the change to add the tags to - * @param int $rev_id rev_id of the change to add the tags to - * @param int $log_id Log_id of the change to add the tags to + * @param int|null $rc_id rc_id of the change to add the tags to + * @param int|null $rev_id rev_id of the change to add the tags to + * @param int|null $log_id Log_id of the change to add the tags to * @param string $params params to put in the ct_params field of table 'change_tag' * * @throws MWException @@ -143,7 +143,7 @@ class ChangeTags { 'ts_log_id' => $log_id ) ); - ## Update the summary row. + // Update the summary row. // $prevTags can be out of date on slaves, especially when addTags is called consecutively, // causing loss of tags added recently in tag_summary table. $prevTags = $dbw->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ ); diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index a28f2164b3..51ebd57bae 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1217,6 +1217,33 @@ $wgThumbLimits = array( 300 ); +/** + * When defined, is an array of image widths used as buckets for thumbnail generation. + * The goal is to save resources by generating thumbnails based on reference buckets instead of + * always using the original. This will incur a speed gain but cause a quality loss. + * + * The buckets generation is chained, with each bucket generated based on the above bucket + * when possible. File handlers have to opt into using that feature. For now only BitmapHandler + * supports it. + */ +$wgThumbnailBuckets = null; + +/** + * When using thumbnail buckets as defined above, this sets the minimum distance with the bucket + * above the requested size. The distance represents how pany extra pixels of width the bucket needs + * in order to be used as the reference for a given thumbnail. For example, with the following buckets: + * + * $wgThumbnailBuckets = array ( 128, 256, 512 ); + * + * and a distance of 50: + * + * $wgThumbnailMinimumBucketDistance = 50; + * + * If we want to render a thumbnail of width 220px, the 512px bucket will be used, + * because 220 + 50 = 270 and the closest bucket bigger than 270px is 512. + */ +$wgThumbnailMinimumBucketDistance = 0; + /** * Default parameters for the "" tag */ @@ -1514,7 +1541,7 @@ $wgUsersNotifiedOnAllChanges = array(); $wgDBserver = 'localhost'; /** - * Database port number (for PostgreSQL) + * Database port number (for PostgreSQL and Microsoft SQL Server). */ $wgDBport = 5432; @@ -1540,11 +1567,21 @@ $wgDBtype = 'mysql'; /** * Whether to use SSL in DB connection. + * + * This setting is only used $wgLBFactoryConf['class'] is set to + * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise + * the DBO_SSL flag must be set in the 'flags' option of the database + * connection to achieve the same functionality. */ $wgDBssl = false; /** * Whether to use compression in DB connection. + * + * This setting is only used $wgLBFactoryConf['class'] is set to + * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise + * the DBO_COMPRESS flag must be set in the 'flags' option of the database + * connection to achieve the same functionality. */ $wgDBcompress = false; @@ -1652,7 +1689,7 @@ $wgSharedTables = array( 'user', 'user_properties' ); * - dbname: Default database name * - user: DB user * - password: DB password - * - type: "mysql" or "postgres" + * - type: DB type * * - load: Ratio of DB_SLAVE load, must be >=0, the sum of all loads must be >0. * If this is zero for any given server, no normal query traffic will be @@ -2153,6 +2190,12 @@ $wgCachePages = true; */ $wgCacheEpoch = '20030516000000'; +/** + * Directory where GitInfo will look for pre-computed cache files. If false, + * $wgCacheDirectory/gitinfo will be used. + */ +$wgGitInfoCacheDirectory = false; + /** * Bump this number when changing the global style sheets and JavaScript. * @@ -4352,8 +4395,9 @@ $wgGroupPermissions['sysop']['noratelimit'] = true; $wgGroupPermissions['sysop']['movefile'] = true; $wgGroupPermissions['sysop']['unblockself'] = true; $wgGroupPermissions['sysop']['suppressredirect'] = true; +#$wgGroupPermissions['sysop']['pagelang'] = true; #$wgGroupPermissions['sysop']['upload_by_url'] = true; -#$wgGroupPermissions['sysop']['mergehistory'] = true; +$wgGroupPermissions['sysop']['mergehistory'] = true; // Permission to change users' group assignments $wgGroupPermissions['bureaucrat']['userrights'] = true; @@ -4970,7 +5014,12 @@ $wgDebugComments = false; $wgDebugDBTransactions = false; /** - * Write SQL queries to the debug log + * Write SQL queries to the debug log. + * + * This setting is only used $wgLBFactoryConf['class'] is set to + * 'LBFactorySimple' and $wgDBservers is an empty array; otherwise + * the DBO_DEBUG flag must be set in the 'flags' option of the database + * connection to achieve the same functionality. */ $wgDebugDumpSql = false; @@ -5262,18 +5311,6 @@ $wgAdvancedSearchHighlighting = false; */ $wgSearchHighlightBoundaries = '[\p{Z}\p{P}\p{C}]'; -/** - * Set to true to have the search engine count total - * search matches to present in the Special:Search UI. - * Not supported by every search engine shipped with MW. - * - * This could however be slow on larger wikis, and is pretty flaky - * with the current title vs content split. Recommend avoiding until - * that's been worked out cleanly; but this may aid in testing the - * search UI and API to confirm that the result count works. - */ -$wgCountTotalSearchHits = false; - /** * Template for OpenSearch suggestions, defaults to API action=opensearch * @@ -5585,9 +5622,11 @@ $wgRC2UDPOmitBots = false; * Destinations to which notifications about recent changes * should be sent. * - * As of MediaWiki 1.22, the only supported 'engine' parameter option in core - * is 'UDPRCFeedEngine', which is used to send recent changes over UDP to the - * specified server. + * As of MediaWiki 1.22, there are 2 supported 'engine' parameter option in core: + * * 'UDPRCFeedEngine', which is used to send recent changes over UDP to the + * specified server. + * * 'RedisPubSubFeedEngine', which is used to send recent changes to Redis. + * * The common options are: * * 'uri' -- the address to which the notices are to be sent. * * 'formatter' -- the class name (implementing RCFeedFormatter) which will @@ -5597,10 +5636,12 @@ $wgRC2UDPOmitBots = false; * * 'omit_user' -- whether edits by registered users should be in the feed * * 'omit_minor' -- whether minor edits should be in the feed * * 'omit_patrolled' -- whether patrolled edits should be in the feed + * * The IRC-specific options are: * * 'add_interwiki_prefix' -- whether the titles should be prefixed with * the first entry in the $wgLocalInterwikis array (or the value of * $wgLocalInterwiki, if set) + * * The JSON-specific options are: * * 'channel' -- if set, the 'channel' parameter is also set in JSON values. * @@ -7098,6 +7139,14 @@ $wgHttpsPort = 443; $wgHKDFSecret = false; $wgHKDFAlgorithm = 'sha256'; +/** + * Enable page language feature + * Allows setting page language in database + * @var bool + * @since 1.24 + */ +$wgPageLanguageUseDB = false; + /** * For really cool vim folding this needs to be at the end: * vim: foldmarker=@{,@} foldmethod=marker diff --git a/includes/EditPage.php b/includes/EditPage.php index 46423203ee..3d57e95895 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -2236,14 +2236,15 @@ class EditPage { $username = $parts[0]; $user = User::newFromName( $username, false /* allow IP users*/ ); $ip = User::isIP( $username ); + $block = Block::newFromTarget( $user, $user ); if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist $wgOut->wrapWikiMsg( "
\n$1\n
", array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) ); - } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked + } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked LogEventsList::showLogExtract( $wgOut, 'block', - $user->getUserPage(), + MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(), '', array( 'lim' => 1, @@ -3838,46 +3839,34 @@ HTML public function getEditButtons( &$tabindex ) { $buttons = array(); - // @todo FIXME: Hardcoded square brackets. - $temp = array( + $attribs = array( 'id' => 'wpSave', 'name' => 'wpSave', 'type' => 'submit', 'tabindex' => ++$tabindex, 'value' => wfMessage( 'savearticle' )->text(), - 'accesskey' => wfMessage( 'accesskey-save' )->text(), - 'title' => wfMessage( 'tooltip-save' )->text() - . ' [' . wfMessage( 'accesskey-save' )->text() . ']', - ); - $buttons['save'] = Xml::element( 'input', $temp, '' ); + ) + Linker::tooltipAndAccesskeyAttribs( 'save' ); + $buttons['save'] = Xml::element( 'input', $attribs, '' ); ++$tabindex; // use the same for preview and live preview - // @todo FIXME: Hardcoded square brackets. - $temp = array( + $attribs = array( 'id' => 'wpPreview', 'name' => 'wpPreview', 'type' => 'submit', 'tabindex' => $tabindex, 'value' => wfMessage( 'showpreview' )->text(), - 'accesskey' => wfMessage( 'accesskey-preview' )->text(), - 'title' => wfMessage( 'tooltip-preview' )->text() - . ' [' . wfMessage( 'accesskey-preview' )->text() . ']', - ); - $buttons['preview'] = Xml::element( 'input', $temp, '' ); + ) + Linker::tooltipAndAccesskeyAttribs( 'preview' ); + $buttons['preview'] = Xml::element( 'input', $attribs, '' ); $buttons['live'] = ''; - // @todo FIXME: Hardcoded square brackets. - $temp = array( + $attribs = array( 'id' => 'wpDiff', 'name' => 'wpDiff', 'type' => 'submit', 'tabindex' => ++$tabindex, 'value' => wfMessage( 'showdiff' )->text(), - 'accesskey' => wfMessage( 'accesskey-diff' )->text(), - 'title' => wfMessage( 'tooltip-diff' )->text() - . ' [' . wfMessage( 'accesskey-diff' )->text() . ']', - ); - $buttons['diff'] = Xml::element( 'input', $temp, '' ); + ) + Linker::tooltipAndAccesskeyAttribs( 'diff' ); + $buttons['diff'] = Xml::element( 'input', $attribs, '' ); wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) ); return $buttons; diff --git a/includes/GitInfo.php b/includes/GitInfo.php index 304c1bce30..acf1bf64a3 100644 --- a/includes/GitInfo.php +++ b/includes/GitInfo.php @@ -57,6 +57,9 @@ class GitInfo { */ public function __construct( $repoDir, $usePrecomputed = true ) { $this->cacheFile = self::getCacheFilePath( $repoDir ); + wfDebugLog( 'gitinfo', + "Computed cacheFile={$this->cacheFile} for {$repoDir}" + ); if ( $usePrecomputed && $this->cacheFile !== null && is_readable( $this->cacheFile ) @@ -65,9 +68,11 @@ class GitInfo { file_get_contents( $this->cacheFile ), true ); + wfDebugLog( 'gitinfo', "Loaded git data from cache for {$repoDir}" ); } if ( !$this->cacheIsComplete() ) { + wfDebugLog( 'gitinfo', "Cache incomplete for {$repoDir}" ); $this->basedir = $repoDir . DIRECTORY_SEPARATOR . '.git'; if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) { $GITfile = file_get_contents( $this->basedir ); @@ -90,24 +95,31 @@ class GitInfo { * Compute the path to the cache file for a given directory. * * @param string $repoDir The root directory of the repo where .git can be found - * @return string Path to GitInfo cache file in $wgCacheDirectory or null if - * $wgCacheDirectory is false (cache disabled). + * @return string Path to GitInfo cache file in $wgGitInfoCacheDirectory or + * null if $wgGitInfoCacheDirectory is false (cache disabled). + * @since 1.24 */ protected static function getCacheFilePath( $repoDir ) { - global $IP, $wgCacheDirectory; - if ( $wgCacheDirectory ) { - // Transform path to git repo to something we can safely embed in a filename - $repoName = $repoDir; - if ( strpos( $repoName, $IP ) === 0 ) { + global $IP, $wgGitInfoCacheDirectory; + + if ( $wgGitInfoCacheDirectory ) { + // Convert both $IP and $repoDir to canonical paths to protect against + // $IP having changed between the settings files and runtime. + $realIP = realpath( $IP ); + $repoName = realpath( $repoDir ); + if ( $repoName === false ) { + // Unit tests use fake path names + $repoName = $repoDir; + } + if ( strpos( $repoName, $realIP ) === 0 ) { // Strip $IP from path - $repoName = substr( $repoName, strlen( $IP ) ); + $repoName = substr( $repoName, strlen( $realIP ) ); } + // Transform path to git repo to something we can safely embed in + // a filename $repoName = strtr( $repoName, DIRECTORY_SEPARATOR, '-' ); $fileName = 'info' . $repoName . '.json'; - return implode( - DIRECTORY_SEPARATOR, - array( $wgCacheDirectory, 'gitinfo', $fileName ) - ); + return "{$wgGitInfoCacheDirectory}/{$fileName}"; } return null; } @@ -330,7 +342,9 @@ class GitInfo { $this->getRemoteUrl(); if ( !$this->cacheIsComplete() ) { - wfDebugLog( "Failed to compute GitInfo for \"{$this->basedir}\"" ); + wfDebugLog( 'gitinfo', + "Failed to compute GitInfo for \"{$this->basedir}\"" + ); return; } diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index c67cbd5ca7..cb5b7fdf61 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -2097,9 +2097,11 @@ function wfClientAcceptsGzip( $force = false ) { * @param int $deflimit Default limit if none supplied * @param string $optionname Name of a user preference to check against * @return array + * @deprecated since 1.24, just call WebRequest::getLimitOffset() directly */ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) { global $wgRequest; + wfDeprecated( __METHOD__, '1.24' ); return $wgRequest->getLimitOffset( $deflimit, $optionname ); } @@ -2578,10 +2580,12 @@ function wfIsHHVM() { /** * Swap two variables * + * @deprecated since 1.24 * @param mixed $x * @param mixed $y */ function swap( &$x, &$y ) { + wfDeprecated( __FUNCTION__, '1.24' ); $z = $x; $x = $y; $y = $z; diff --git a/includes/Hooks.php b/includes/Hooks.php index 89457e8436..77486b208c 100644 --- a/includes/Hooks.php +++ b/includes/Hooks.php @@ -48,7 +48,7 @@ class Hooks { * Attach an event handler to a given hook. * * @param string $name Name of hook - * @param mixed $callback Callback function to attach + * @param callable $callback Callback function to attach * * @since 1.18 */ diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php deleted file mode 100644 index b0266cbaf7..0000000000 --- a/includes/ImageQueryPage.php +++ /dev/null @@ -1,80 +0,0 @@ - - */ -abstract class ImageQueryPage extends QueryPage { - /** - * Format and output report results using the given information plus - * OutputPage - * - * @param OutputPage $out OutputPage to print to - * @param Skin $skin User skin to use [unused] - * @param DatabaseBase $dbr (read) connection to use - * @param int $res Result pointer - * @param int $num Number of available result rows - * @param int $offset Paging offset - */ - protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { - if ( $num > 0 ) { - $gallery = ImageGalleryBase::factory(); - $gallery->setContext( $this->getContext() ); - - # $res might contain the whole 1,000 rows, so we read up to - # $num [should update this to use a Pager] - $i = 0; - foreach ( $res as $row ) { - $i++; - $namespace = isset( $row->namespace ) ? $row->namespace : NS_FILE; - $title = Title::makeTitleSafe( $namespace, $row->title ); - if ( $title instanceof Title && $title->getNamespace() == NS_FILE ) { - $gallery->add( $title, $this->getCellHtml( $row ) ); - } - if ( $i === $num ) { - break; - } - } - - $out->addHTML( $gallery->toHtml() ); - } - } - - // Gotta override this since it's abstract - function formatResult( $skin, $result ) { - } - - /** - * Get additional HTML to be shown in a results' cell - * - * @param object $row Result row - * @return string - */ - protected function getCellHtml( $row ) { - return ''; - } -} diff --git a/includes/MWTimestamp.php b/includes/MWTimestamp.php index 447dde3cb9..ad3228d9a0 100644 --- a/includes/MWTimestamp.php +++ b/includes/MWTimestamp.php @@ -268,7 +268,7 @@ class MWTimestamp { // first value. if ( $data[0] == 'System' ) { // First value is System, so use the system offset. - if ( isset( $wgLocalTZoffset ) ) { + if ( $wgLocalTZoffset !== null ) { $diff = $wgLocalTZoffset; } } elseif ( $data[0] == 'Offset' ) { diff --git a/includes/MagicWord.php b/includes/MagicWord.php index 3e327c3559..7decbee0ab 100644 --- a/includes/MagicWord.php +++ b/includes/MagicWord.php @@ -96,6 +96,7 @@ class MagicWord { static public $mVariableIDsInitialised = false; static public $mVariableIDs = array( + '!', 'currentmonth', 'currentmonth1', 'currentmonthname', diff --git a/includes/Message.php b/includes/Message.php index 826d55bfbc..950bcd5d20 100644 --- a/includes/Message.php +++ b/includes/Message.php @@ -995,7 +995,7 @@ class Message { * @throws MWException If message key array is empty. */ protected function fetchMessage() { - if ( !isset( $this->message ) ) { + if ( $this->message === null ) { $cache = MessageCache::singleton(); if ( is_array( $this->key ) ) { if ( !count( $this->key ) ) { @@ -1054,7 +1054,7 @@ class RawMessage extends Message { */ public function fetchMessage() { // Just in case the message is unset somewhere. - if ( !isset( $this->message ) ) { + if ( $this->message === null ) { $this->message = $this->key; } return $this->message; diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index f4d4697c38..b4d3ab1c1e 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -164,6 +164,14 @@ class MimeMagic { */ protected $mIEAnalyzer; + /** @var string Extra MIME types, set for example by media handling extensions + */ + private $mExtraTypes = ''; + + /** @var string Extra MIME info, set for example by media handling extensions + */ + private $mExtraInfo = ''; + /** @var MimeMagic The singleton instance */ private static $instance = null; @@ -179,6 +187,9 @@ class MimeMagic { global $wgMimeTypeFile, $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' ) { @@ -197,11 +208,13 @@ class MimeMagic { wfDebug( __METHOD__ . ": no mime types file defined, using build-ins only.\n" ); } + $types .= "\n" . $this->mExtraTypes; + $types = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $types ); $types = str_replace( "\t", " ", $types ); $this->mMimeToExt = array(); - $this->mToMime = array(); + $this->mExtToMime = array(); $lines = explode( "\n", $types ); foreach ( $lines as $s ) { @@ -272,6 +285,8 @@ class MimeMagic { wfDebug( __METHOD__ . ": no mime info file defined, using build-ins only.\n" ); } + $info .= "\n" . $this->mExtraInfo; + $info = str_replace( array( "\r\n", "\n\r", "\n\n", "\r\r", "\r" ), "\n", $info ); $info = str_replace( "\t", " ", $info ); @@ -342,6 +357,26 @@ class MimeMagic { return self::$instance; } + /** + * Adds to the list mapping MIME to file extensions. + * As an extension author, you are encouraged to submit patches to + * MediaWiki's core to add new MIME types to mime.types. + * @param string $types + */ + public function addExtraTypes( $types ) { + $this->mExtraTypes .= "\n" . $types; + } + + /** + * Adds to the list mapping MIME to media type. + * As an extension author, you are encouraged to submit patches to + * MediaWiki's core to add new MIME info to mime.info. + * @param string $info + */ + public function addExtraInfo( $info ) { + $this->mExtraInfo .= "\n" . $info; + } + /** * Returns a list of file extensions for a given mime type as a space * separated string or null if the mime type was unrecognized. Resolves @@ -485,14 +520,6 @@ class MimeMagic { * by looking at the file extension. Typically, this method would be called on the * result of guessMimeType(). * - * Currently, this method does the following: - * - * If $mime is "unknown/unknown" and isRecognizableExtension( $ext ) returns false, - * return the result of guessTypesForExtension($ext). - * - * If $mime is "application/x-opc+zip" and isMatchingExtension( $ext, $mime ) - * gives true, return the result of guessTypesForExtension($ext). - * * @param string $mime The mime type, typically guessed from a file's content. * @param string $ext The file extension, as taken from the file name * @@ -518,8 +545,17 @@ class MimeMagic { ".$ext is not a known OPC extension.\n" ); $mime = 'application/zip'; } + } elseif ( $mime === 'text/plain' && $this->findMediaType( ".$ext" ) === MEDIATYPE_TEXT ) { + // Textual types are sometimes not recognized properly. + // If detected as text/plain, and has an extension which is textual + // improve to the extension's type. For example, csv and json are often + // misdetected as text/plain. + $mime = $this->guessTypesForExtension( $ext ); } + # Media handling extensions can improve the MIME detected + wfRunHooks( 'MimeMagicImproveFromExtension', array( $this, $ext, &$mime ) ); + if ( isset( $this->mMimeTypeAliases[$mime] ) ) { $mime = $this->mMimeTypeAliases[$mime]; } @@ -746,7 +782,17 @@ class MimeMagic { return 'image/vnd.djvu'; } - return false; + # Media handling extensions can guess the MIME by content + # It's intentionally here so that if core is wrong about a type (false positive), + # people will hopefully nag and submit patches :) + $mime = false; + # Some strings by reference for performance - assuming well-behaved hooks + wfRunHooks( + 'MimeMagicGuessFromContent', + array( $this, &$head, &$tail, $file, &$mime ) + ); + + return $mime; } /** @@ -994,7 +1040,7 @@ class MimeMagic { $head = fread( $f, 256 ); fclose( $f ); - $head = strtolower( $head ); + $head = str_replace( 'ffmpeg2theora', '', strtolower( $head ) ); // This is an UGLY HACK, file should be parsed correctly if ( strpos( $head, 'theora' ) !== false ) { diff --git a/includes/Namespace.php b/includes/Namespace.php index 4edddbc9b0..392f5582f8 100644 --- a/includes/Namespace.php +++ b/includes/Namespace.php @@ -88,16 +88,6 @@ class MWNamespace { return !self::isTalk( $index ); } - /** - * @see self::isSubject - * @deprecated since 1.19 Please use the more consistently named isSubject - * @return bool - */ - public static function isMain( $index ) { - wfDeprecated( __METHOD__, '1.19' ); - return self::isSubject( $index ); - } - /** * Is the given namespace a talk namespace? * diff --git a/includes/OutputPage.php b/includes/OutputPage.php index a2b8920a49..89679382cf 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -885,10 +885,7 @@ class OutputPage extends ContextSource { * @return string */ public function getPageTitleActionText() { - if ( isset( $this->mPageTitleActionText ) ) { - return $this->mPageTitleActionText; - } - return ''; + return $this->mPageTitleActionText; } /** @@ -1345,18 +1342,6 @@ class OutputPage extends ContextSource { ); } - /** - * Return whether user JavaScript is allowed for this page - * @deprecated since 1.18 Load modules with ResourceLoader, and origin and - * trustworthiness is identified and enforced automagically. - * @return bool - */ - public function isUserJsAllowed() { - wfDeprecated( __METHOD__, '1.18' ); - return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= - ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL; - } - /** * Show what level of JavaScript / CSS untrustworthiness is allowed on this page * @see ResourceLoaderModule::$origin @@ -1608,7 +1593,7 @@ class OutputPage extends ContextSource { $oldTidy = $popts->setTidy( $tidy ); $popts->setInterfaceMessage( (bool)$interface ); - $parserOutput = $wgParser->parse( + $parserOutput = $wgParser->getFreshParser()->parse( $text, $title, $popts, $linestart, true, $this->mRevisionId ); @@ -1768,7 +1753,7 @@ class OutputPage extends ContextSource { $oldLang = $popts->setTargetLanguage( $language ); } - $parserOutput = $wgParser->parse( + $parserOutput = $wgParser->getFreshParser()->parse( $text, $this->getTitle(), $popts, $linestart, true, $this->mRevisionId ); @@ -2069,19 +2054,6 @@ class OutputPage extends ContextSource { } } - /** - * Get the message associated with the HTTP response code $code - * - * @param int $code Status code - * @return string|null Message or null if $code is not in the list of messages - * - * @deprecated since 1.18 Use HttpStatus::getMessage() instead. - */ - public static function getStatusMessage( $code ) { - wfDeprecated( __METHOD__, '1.18' ); - return HttpStatus::getMessage( $code ); - } - /** * Finally, all the text has been munged and accumulated into * the object, let's actually output it: @@ -3112,16 +3084,13 @@ $templates /** * Get an array containing the variables to be set in mw.config in JavaScript. * - * DO NOT CALL THIS FROM OUTSIDE OF THIS CLASS OR Skin::makeGlobalVariablesScript(). - * This is only public until that function is removed. You have been warned. - * * Do not add things here which can be evaluated in ResourceLoaderStartUpModule * - in other words, page-independent/site-wide variables (without state). * You will only be adding bloat to the html page and causing page caches to * have to be purged on configuration changes. * @return array */ - public function getJSVars() { + private function getJSVars() { global $wgContLang; $curRevisionId = 0; diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php index 0fb3952bd4..e475f0b862 100644 --- a/includes/PHPVersionError.php +++ b/includes/PHPVersionError.php @@ -32,6 +32,7 @@ * - index.php * - load.php * - api.php + * - mw-config/index.php * - cli * * @note Since we can't rely on anything, the minimum PHP versions and MW current @@ -50,10 +51,15 @@ function wfPHPVersionError( $type ) { $finalOutput = "You are using PHP version $phpVersion " . "but MediaWiki $mwVersion needs PHP $minimumVersionPHP or higher. ABORTING.\n" . "Check if you have a newer php executable with a different name, such as php5.\n"; - } elseif ( $type == 'index.php' ) { + } elseif ( $type == 'index.php' || $type == 'mw-config/index.php' ) { $pathinfo = pathinfo( $_SERVER['SCRIPT_NAME'] ); + if ( $type == 'mw-config/index.php' ) { + $dirname = dirname( $pathinfo['dirname'] ); + } else { + $dirname = $pathinfo['dirname']; + } $encLogo = htmlspecialchars( - str_replace( '//', '/', $pathinfo['dirname'] . '/' ) . + str_replace( '//', '/', $dirname . '/' ) . 'skins/common/images/mediawiki.png' ); diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php deleted file mode 100644 index afc02271c5..0000000000 --- a/includes/PageQueryPage.php +++ /dev/null @@ -1,72 +0,0 @@ -numRows() ) { - return; - } - - $batch = new LinkBatch(); - foreach ( $res as $row ) { - $batch->add( $row->namespace, $row->title ); - } - $batch->execute(); - - $res->seek( 0 ); - } - - /** - * Format the result as a simple link to the page - * - * @param Skin $skin - * @param object $row Result row - * @return string - */ - public function formatResult( $skin, $row ) { - global $wgContLang; - - $title = Title::makeTitleSafe( $row->namespace, $row->title ); - - if ( $title instanceof Title ) { - $text = $wgContLang->convert( $title->getPrefixedText() ); - return Linker::link( $title, htmlspecialchars( $text ) ); - } else { - return Html::element( 'span', array( 'class' => 'mw-invalidtitle' ), - Linker::getInvalidTitleDescription( $this->getContext(), $row->namespace, $row->title ) ); - } - } -} diff --git a/includes/Preferences.php b/includes/Preferences.php index 081315e2a2..fdb1a9db66 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -98,6 +98,20 @@ class Preferences { wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) ); + self::loadPreferenceValues( $user, $context, $defaultPreferences ); + self::$defaultPreferences = $defaultPreferences; + return $defaultPreferences; + } + + /** + * Loads existing values for a given array of preferences + * @throws MWException + * @param User $user + * @param IContextSource $context + * @param array defaultPreferences to load values for + * @return array|null + */ + static function loadPreferenceValues( $user, $context, &$defaultPreferences ) { ## Remove preferences that wikis don't want to use global $wgHiddenPrefs; foreach ( $wgHiddenPrefs as $pref ) { @@ -138,8 +152,6 @@ class Preferences { } } - self::$defaultPreferences = $defaultPreferences; - return $defaultPreferences; } @@ -1463,27 +1475,6 @@ class Preferences { return array( $result, 'mailerror' ); } } - - /** - * @deprecated since 1.19 - * @param User $user - * @return array - */ - public static function loadOldSearchNs( $user ) { - wfDeprecated( __METHOD__, '1.19' ); - - $searchableNamespaces = SearchEngine::searchableNamespaces(); - // Back compat with old format - $arr = array(); - - foreach ( $searchableNamespaces as $ns => $name ) { - if ( $user->getOption( 'searchNs' . $ns ) ) { - $arr[] = $ns; - } - } - - return $arr; - } } /** Some tweaks to allow js prefs to work */ diff --git a/includes/QueryPage.php b/includes/QueryPage.php deleted file mode 100644 index 82dea0d96f..0000000000 --- a/includes/QueryPage.php +++ /dev/null @@ -1,855 +0,0 @@ -listoutput; - * - * @param bool $bool - */ - function setListoutput( $bool ) { - $this->listoutput = $bool; - } - - /** - * Subclasses return an SQL query here, formatted as an array with the - * following keys: - * tables => Table(s) for passing to Database::select() - * fields => Field(s) for passing to Database::select(), may be * - * conds => WHERE conditions - * options => options - * join_conds => JOIN conditions - * - * Note that the query itself should return the following three columns: - * 'namespace', 'title', and 'value'. 'value' is used for sorting. - * - * These may be stored in the querycache table for expensive queries, - * and that cached data will be returned sometimes, so the presence of - * extra fields can't be relied upon. The cached 'value' column will be - * an integer; non-numeric values are useful only for sorting the - * initial query (except if they're timestamps, see usesTimestamps()). - * - * Don't include an ORDER or LIMIT clause, they will be added. - * - * If this function is not overridden or returns something other than - * an array, getSQL() will be used instead. This is for backwards - * compatibility only and is strongly deprecated. - * @return array - * @since 1.18 - */ - function getQueryInfo() { - return null; - } - - /** - * For back-compat, subclasses may return a raw SQL query here, as a string. - * This is strongly deprecated; getQueryInfo() should be overridden instead. - * @throws MWException - * @return string - */ - function getSQL() { - /* Implement getQueryInfo() instead */ - throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor " - . "getQuery() properly" ); - } - - /** - * Subclasses return an array of fields to order by here. Don't append - * DESC to the field names, that'll be done automatically if - * sortDescending() returns true. - * @return array - * @since 1.18 - */ - function getOrderFields() { - return array( 'value' ); - } - - /** - * Does this query return timestamps rather than integers in its - * 'value' field? If true, this class will convert 'value' to a - * UNIX timestamp for caching. - * NOTE: formatRow() may get timestamps in TS_MW (mysql), TS_DB (pgsql) - * or TS_UNIX (querycache) format, so be sure to always run them - * through wfTimestamp() - * @return bool - * @since 1.18 - */ - function usesTimestamps() { - return false; - } - - /** - * Override to sort by increasing values - * - * @return bool - */ - function sortDescending() { - return true; - } - - /** - * Is this query expensive (for some definition of expensive)? Then we - * don't let it run in miser mode. $wgDisableQueryPages causes all query - * pages to be declared expensive. Some query pages are always expensive. - * - * @return bool - */ - function isExpensive() { - global $wgDisableQueryPages; - return $wgDisableQueryPages; - } - - /** - * Is the output of this query cacheable? Non-cacheable expensive pages - * will be disabled in miser mode and will not have their results written - * to the querycache table. - * @return bool - * @since 1.18 - */ - public function isCacheable() { - return true; - } - - /** - * Whether or not the output of the page in question is retrieved from - * the database cache. - * - * @return bool - */ - function isCached() { - global $wgMiserMode; - - return $this->isExpensive() && $wgMiserMode; - } - - /** - * Sometime we don't want to build rss / atom feeds. - * - * @return bool - */ - function isSyndicated() { - return true; - } - - /** - * Formats the results of the query for display. The skin is the current - * skin; you can use it for making links. The result is a single row of - * result data. You should be able to grab SQL results off of it. - * If the function returns false, the line output will be skipped. - * @param Skin $skin - * @param object $result Result row - * @return string|bool String or false to skip - */ - abstract function formatResult( $skin, $result ); - - /** - * The content returned by this function will be output before any result - * - * @return string - */ - function getPageHeader() { - return ''; - } - - /** - * If using extra form wheely-dealies, return a set of parameters here - * as an associative array. They will be encoded and added to the paging - * links (prev/next/lengths). - * - * @return array - */ - function linkParameters() { - return array(); - } - - /** - * Some special pages (for example SpecialListusers) might not return the - * current object formatted, but return the previous one instead. - * Setting this to return true will ensure formatResult() is called - * one more time to make sure that the very last result is formatted - * as well. - * @return bool - */ - function tryLastResult() { - return false; - } - - /** - * Clear the cache and save new results - * - * @param int|bool $limit Limit for SQL statement - * @param bool $ignoreErrors Whether to ignore database errors - * @throws DBError|Exception - * @return bool|int - */ - function recache( $limit, $ignoreErrors = true ) { - if ( !$this->isCacheable() ) { - return 0; - } - - $fname = get_class( $this ) . '::recache'; - $dbw = wfGetDB( DB_MASTER ); - if ( !$dbw ) { - return false; - } - - try { - # Do query - $res = $this->reallyDoQuery( $limit, false ); - $num = false; - if ( $res ) { - $num = $res->numRows(); - # Fetch results - $vals = array(); - foreach ( $res as $row ) { - if ( isset( $row->value ) ) { - if ( $this->usesTimestamps() ) { - $value = wfTimestamp( TS_UNIX, - $row->value ); - } else { - $value = intval( $row->value ); // @bug 14414 - } - } else { - $value = 0; - } - - $vals[] = array( 'qc_type' => $this->getName(), - 'qc_namespace' => $row->namespace, - 'qc_title' => $row->title, - 'qc_value' => $value ); - } - - $dbw->begin( __METHOD__ ); - # Clear out any old cached data - $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); - # Save results into the querycache table on the master - if ( count( $vals ) ) { - $dbw->insert( 'querycache', $vals, __METHOD__ ); - } - # Update the querycache_info record for the page - $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); - $dbw->insert( 'querycache_info', - array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), - $fname ); - $dbw->commit( __METHOD__ ); - } - } catch ( DBError $e ) { - if ( !$ignoreErrors ) { - throw $e; // report query error - } - $num = false; // set result to false to indicate error - } - - return $num; - } - - /** - * Get a DB connection to be used for slow recache queries - * @return DatabaseBase - */ - function getRecacheDB() { - return wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) ); - } - - /** - * Run the query and return the result - * @param int|bool $limit Numerical limit or false for no limit - * @param int|bool $offset Numerical offset or false for no offset - * @return ResultWrapper - * @since 1.18 - */ - function reallyDoQuery( $limit, $offset = false ) { - $fname = get_class( $this ) . "::reallyDoQuery"; - $dbr = $this->getRecacheDB(); - $query = $this->getQueryInfo(); - $order = $this->getOrderFields(); - - if ( $this->sortDescending() ) { - foreach ( $order as &$field ) { - $field .= ' DESC'; - } - } - - if ( is_array( $query ) ) { - $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array(); - $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array(); - $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array(); - $options = isset( $query['options'] ) ? (array)$query['options'] : array(); - $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array(); - - if ( count( $order ) ) { - $options['ORDER BY'] = $order; - } - - if ( $limit !== false ) { - $options['LIMIT'] = intval( $limit ); - } - - if ( $offset !== false ) { - $options['OFFSET'] = intval( $offset ); - } - - $res = $dbr->select( $tables, $fields, $conds, $fname, - $options, $join_conds - ); - } else { - // Old-fashioned raw SQL style, deprecated - $sql = $this->getSQL(); - $sql .= ' ORDER BY ' . implode( ', ', $order ); - $sql = $dbr->limitResult( $sql, $limit, $offset ); - $res = $dbr->query( $sql, $fname ); - } - - return $dbr->resultObject( $res ); - } - - /** - * Somewhat deprecated, you probably want to be using execute() - * @param int|bool $offset - * @param int|bool $limit - * @return ResultWrapper - */ - function doQuery( $offset = false, $limit = false ) { - if ( $this->isCached() && $this->isCacheable() ) { - return $this->fetchFromCache( $limit, $offset ); - } else { - return $this->reallyDoQuery( $limit, $offset ); - } - } - - /** - * Fetch the query results from the query cache - * @param int|bool $limit Numerical limit or false for no limit - * @param int|bool $offset Numerical offset or false for no offset - * @return ResultWrapper - * @since 1.18 - */ - function fetchFromCache( $limit, $offset = false ) { - $dbr = wfGetDB( DB_SLAVE ); - $options = array(); - if ( $limit !== false ) { - $options['LIMIT'] = intval( $limit ); - } - if ( $offset !== false ) { - $options['OFFSET'] = intval( $offset ); - } - if ( $this->sortDescending() ) { - $options['ORDER BY'] = 'qc_value DESC'; - } else { - $options['ORDER BY'] = 'qc_value ASC'; - } - $res = $dbr->select( 'querycache', array( 'qc_type', - 'namespace' => 'qc_namespace', - 'title' => 'qc_title', - 'value' => 'qc_value' ), - array( 'qc_type' => $this->getName() ), - __METHOD__, $options - ); - return $dbr->resultObject( $res ); - } - - public function getCachedTimestamp() { - if ( is_null( $this->cachedTimestamp ) ) { - $dbr = wfGetDB( DB_SLAVE ); - $fname = get_class( $this ) . '::getCachedTimestamp'; - $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp', - array( 'qci_type' => $this->getName() ), $fname ); - } - return $this->cachedTimestamp; - } - - /** - * This is the actual workhorse. It does everything needed to make a - * real, honest-to-gosh query page. - * @param string $par - * @return int - */ - function execute( $par ) { - global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; - - $user = $this->getUser(); - if ( !$this->userCanExecute( $user ) ) { - $this->displayRestrictionError(); - return; - } - - $this->setHeaders(); - $this->outputHeader(); - - $out = $this->getOutput(); - - if ( $this->isCached() && !$this->isCacheable() ) { - $out->addWikiMsg( 'querypage-disabled' ); - return 0; - } - - $out->setSyndicated( $this->isSyndicated() ); - - if ( $this->limit == 0 && $this->offset == 0 ) { - list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset(); - } - - // @todo Use doQuery() - if ( !$this->isCached() ) { - # select one extra row for navigation - $res = $this->reallyDoQuery( $this->limit + 1, $this->offset ); - } else { - # Get the cached result, select one extra row for navigation - $res = $this->fetchFromCache( $this->limit + 1, $this->offset ); - if ( !$this->listoutput ) { - - # Fetch the timestamp of this update - $ts = $this->getCachedTimestamp(); - $lang = $this->getLanguage(); - $maxResults = $lang->formatNum( $wgQueryCacheLimit ); - - if ( $ts ) { - $updated = $lang->userTimeAndDate( $ts, $user ); - $updateddate = $lang->userDate( $ts, $user ); - $updatedtime = $lang->userTime( $ts, $user ); - $out->addMeta( 'Data-Cache-Time', $ts ); - $out->addJsConfigVars( 'dataCacheTime', $ts ); - $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults ); - } else { - $out->addWikiMsg( 'perfcached', $maxResults ); - } - - # If updates on this page have been disabled, let the user know - # that the data set won't be refreshed for now - if ( is_array( $wgDisableQueryPageUpdate ) - && in_array( $this->getName(), $wgDisableQueryPageUpdate ) - ) { - $out->wrapWikiMsg( - "
\n$1\n
", - 'querypage-no-updates' - ); - } - } - } - - $this->numRows = $res->numRows(); - - $dbr = wfGetDB( DB_SLAVE ); - $this->preprocessResults( $dbr, $res ); - - $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) ); - - # Top header and navigation - if ( $this->shownavigation ) { - $out->addHTML( $this->getPageHeader() ); - if ( $this->numRows > 0 ) { - $out->addHTML( $this->msg( 'showingresultsinrange' )->numParams( - min( $this->numRows, $this->limit ), # do not show the one extra row, if exist - $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() ); - # Disable the "next" link when we reach the end - $paging = $this->getLanguage()->viewPrevNext( $this->getPageTitle( $par ), $this->offset, - $this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) ); - $out->addHTML( '

' . $paging . '

' ); - } else { - # No results to show, so don't bother with "showing X of Y" etc. - # -- just let the user know and give up now - $out->addWikiMsg( 'specialpage-empty' ); - $out->addHTML( Xml::closeElement( 'div' ) ); - return; - } - } - - # The actual results; specialist subclasses will want to handle this - # with more than a straight list, so we hand them the info, plus - # an OutputPage, and let them get on with it - $this->outputResults( $out, - $this->getSkin(), - $dbr, # Should use a ResultWrapper for this - $res, - min( $this->numRows, $this->limit ), # do not format the one extra row, if exist - $this->offset ); - - # Repeat the paging links at the bottom - if ( $this->shownavigation ) { - $out->addHTML( '

' . $paging . '

' ); - } - - $out->addHTML( Xml::closeElement( 'div' ) ); - - return min( $this->numRows, $this->limit ); # do not return the one extra row, if exist - } - - /** - * Format and output report results using the given information plus - * OutputPage - * - * @param OutputPage $out OutputPage to print to - * @param Skin $skin User skin to use - * @param DatabaseBase $dbr Database (read) connection to use - * @param ResultWrapper $res Result pointer - * @param int $num Number of available result rows - * @param int $offset Paging offset - */ - protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { - global $wgContLang; - - if ( $num > 0 ) { - $html = array(); - if ( !$this->listoutput ) { - $html[] = $this->openList( $offset ); - } - - # $res might contain the whole 1,000 rows, so we read up to - # $num [should update this to use a Pager] - // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed - for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) { - // @codingStandardsIgnoreEnd - $line = $this->formatResult( $skin, $row ); - if ( $line ) { - $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) - ? ' class="not-patrolled"' - : ''; - $html[] = $this->listoutput - ? $line - : "{$line}\n"; - } - } - - # Flush the final result - if ( $this->tryLastResult() ) { - $row = null; - $line = $this->formatResult( $skin, $row ); - if ( $line ) { - $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) - ? ' class="not-patrolled"' - : ''; - $html[] = $this->listoutput - ? $line - : "{$line}\n"; - } - } - - if ( !$this->listoutput ) { - $html[] = $this->closeList(); - } - - $html = $this->listoutput - ? $wgContLang->listToText( $html ) - : implode( '', $html ); - - $out->addHTML( $html ); - } - } - - /** - * @param int $offset - * @return string - */ - function openList( $offset ) { - return "\n
    \n"; - } - - /** - * @return string - */ - function closeList() { - return "
\n"; - } - - /** - * Do any necessary preprocessing of the result object. - * @param DatabaseBase $db - * @param ResultWrapper $res - */ - function preprocessResults( $db, $res ) { - } - - /** - * Similar to above, but packaging in a syndicated feed instead of a web page - * @param string $class - * @param int $limit - * @return bool - */ - function doFeed( $class = '', $limit = 50 ) { - global $wgFeed, $wgFeedClasses, $wgFeedLimit; - - if ( !$wgFeed ) { - $this->getOutput()->addWikiMsg( 'feed-unavailable' ); - return false; - } - - $limit = min( $limit, $wgFeedLimit ); - - if ( isset( $wgFeedClasses[$class] ) ) { - /** @var RSSFeed|AtomFeed $feed */ - $feed = new $wgFeedClasses[$class]( - $this->feedTitle(), - $this->feedDesc(), - $this->feedUrl() ); - $feed->outHeader(); - - $res = $this->reallyDoQuery( $limit, 0 ); - foreach ( $res as $obj ) { - $item = $this->feedResult( $obj ); - if ( $item ) { - $feed->outItem( $item ); - } - } - - $feed->outFooter(); - return true; - } else { - return false; - } - } - - /** - * Override for custom handling. If the titles/links are ok, just do - * feedItemDesc() - * @param object $row - * @return FeedItem|null - */ - function feedResult( $row ) { - if ( !isset( $row->title ) ) { - return null; - } - $title = Title::makeTitle( intval( $row->namespace ), $row->title ); - if ( $title ) { - $date = isset( $row->timestamp ) ? $row->timestamp : ''; - $comments = ''; - if ( $title ) { - $talkpage = $title->getTalkPage(); - $comments = $talkpage->getFullURL(); - } - - return new FeedItem( - $title->getPrefixedText(), - $this->feedItemDesc( $row ), - $title->getFullURL(), - $date, - $this->feedItemAuthor( $row ), - $comments ); - } else { - return null; - } - } - - function feedItemDesc( $row ) { - return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; - } - - function feedItemAuthor( $row ) { - return isset( $row->user_text ) ? $row->user_text : ''; - } - - function feedTitle() { - global $wgLanguageCode, $wgSitename; - $desc = $this->getDescription(); - return "$wgSitename - $desc [$wgLanguageCode]"; - } - - function feedDesc() { - return $this->msg( 'tagline' )->text(); - } - - function feedUrl() { - return $this->getPageTitle()->getFullURL(); - } -} - -/** - * Class definition for a wanted query page like - * WantedPages, WantedTemplates, etc - */ -abstract class WantedQueryPage extends QueryPage { - function isExpensive() { - return true; - } - - function isSyndicated() { - return false; - } - - /** - * Cache page existence for performance - * @param DatabaseBase $db - * @param ResultWrapper $res - */ - function preprocessResults( $db, $res ) { - if ( !$res->numRows() ) { - return; - } - - $batch = new LinkBatch; - foreach ( $res as $row ) { - $batch->add( $row->namespace, $row->title ); - } - $batch->execute(); - - // Back to start for display - $res->seek( 0 ); - } - - /** - * Should formatResult() always check page existence, even if - * the results are fresh? This is a (hopefully temporary) - * kluge for Special:WantedFiles, which may contain false - * positives for files that exist e.g. in a shared repo (bug - * 6220). - * @return bool - */ - function forceExistenceCheck() { - return false; - } - - /** - * Format an individual result - * - * @param Skin $skin Skin to use for UI elements - * @param object $result Result row - * @return string - */ - public function formatResult( $skin, $result ) { - $title = Title::makeTitleSafe( $result->namespace, $result->title ); - if ( $title instanceof Title ) { - if ( $this->isCached() || $this->forceExistenceCheck() ) { - $pageLink = $title->isKnown() - ? '' . Linker::link( $title ) . '' - : Linker::link( - $title, - null, - array(), - array(), - array( 'broken' ) - ); - } else { - $pageLink = Linker::link( - $title, - null, - array(), - array(), - array( 'broken' ) - ); - } - return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) ); - } else { - return $this->msg( 'wantedpages-badtitle', $result->title )->escaped(); - } - } - - /** - * Make a "what links here" link for a given title - * - * @param Title $title Title to make the link for - * @param object $result Result row - * @return string - */ - private function makeWlhLink( $title, $result ) { - $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ); - $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped(); - return Linker::link( $wlh, $label ); - } -} diff --git a/includes/Revision.php b/includes/Revision.php index 06f5bd016a..de69827416 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -41,6 +41,11 @@ class Revision implements IDBAccessObject { protected $mParentId; protected $mComment; protected $mText; + protected $mTextId; + + /** + * @var stdClass|null + */ protected $mTextRow; /** @@ -299,7 +304,7 @@ class Revision implements IDBAccessObject { private static function newFromConds( $conditions, $flags = 0 ) { $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE ); $rev = self::loadFromConds( $db, $conditions, $flags ); - if ( is_null( $rev ) && wfGetLB()->getServerCount() > 1 ) { + if ( $rev === null && wfGetLB()->getServerCount() > 1 ) { if ( !( $flags & self::READ_LATEST ) ) { $dbw = wfGetDB( DB_MASTER ); $rev = self::loadFromConds( $dbw, $conditions, $flags ); @@ -566,13 +571,13 @@ class Revision implements IDBAccessObject { $this->mTitle = null; } - if ( !isset( $row->rev_content_model ) || is_null( $row->rev_content_model ) ) { + if ( !isset( $row->rev_content_model ) ) { $this->mContentModel = null; # determine on demand if needed } else { $this->mContentModel = strval( $row->rev_content_model ); } - if ( !isset( $row->rev_content_format ) || is_null( $row->rev_content_format ) ) { + if ( !isset( $row->rev_content_format ) ) { $this->mContentFormat = null; # determine on demand if needed } else { $this->mContentFormat = strval( $row->rev_content_format ); @@ -652,7 +657,7 @@ class Revision implements IDBAccessObject { $this->mContentHandler = null; $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() ); - } elseif ( !is_null( $this->mText ) ) { + } elseif ( $this->mText !== null ) { $handler = $this->getContentHandler(); $this->mContent = $handler->unserializeContent( $this->mText ); } @@ -674,7 +679,7 @@ class Revision implements IDBAccessObject { // If we still have no length, see it we have the text to figure it out if ( !$this->mSize ) { - if ( !is_null( $this->mContent ) ) { + if ( $this->mContent !== null ) { $this->mSize = $this->mContent->getSize(); } else { #NOTE: this should never happen if we have either text or content object! @@ -684,7 +689,7 @@ class Revision implements IDBAccessObject { // Same for sha1 if ( $this->mSha1 === null ) { - $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText ); + $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText ); } // force lazy init @@ -759,11 +764,11 @@ class Revision implements IDBAccessObject { * @return Title|null */ public function getTitle() { - if ( isset( $this->mTitle ) ) { + if ( $this->mTitle !== null ) { return $this->mTitle; } //rev_id is defined as NOT NULL, but this revision may not yet have been inserted. - if ( !is_null( $this->mId ) ) { + if ( $this->mId !== null ) { $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( array( 'page', 'revision' ), @@ -776,7 +781,7 @@ class Revision implements IDBAccessObject { } } - if ( !$this->mTitle && !is_null( $this->mPage ) && $this->mPage > 0 ) { + if ( !$this->mTitle && $this->mPage !== null && $this->mPage > 0 ) { $this->mTitle = Title::newFromID( $this->mPage ); } @@ -1031,7 +1036,7 @@ class Revision implements IDBAccessObject { * @return string */ public function getSerializedData() { - if ( is_null( $this->mText ) ) { + if ( $this->mText === null ) { $this->mText = $this->loadText(); } @@ -1048,9 +1053,9 @@ class Revision implements IDBAccessObject { * @return Content|null The Revision's content, or null on failure. */ protected function getContentInternal() { - if ( is_null( $this->mContent ) ) { + if ( $this->mContent === null ) { // Revision is immutable. Load on demand: - if ( is_null( $this->mText ) ) { + if ( $this->mText === null ) { $this->mText = $this->loadText(); } @@ -1184,7 +1189,7 @@ class Revision implements IDBAccessObject { * @return int */ private function getPreviousRevisionId( $db ) { - if ( is_null( $this->mPage ) ) { + if ( $this->mPage === null ) { return 0; } # Use page_latest if ID is not given @@ -1355,7 +1360,7 @@ class Revision implements IDBAccessObject { } # Record the text (or external storage URL) to the text table - if ( !isset( $this->mTextId ) ) { + if ( $this->mTextId === null ) { $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' ); $dbw->insert( 'text', array( @@ -1372,7 +1377,7 @@ class Revision implements IDBAccessObject { } # Record the edit in revisions - $rev_id = isset( $this->mId ) + $rev_id = $this->mId !== null ? $this->mId : $dbw->nextSequenceValue( 'revision_rev_id_seq' ); $row = array( @@ -1386,10 +1391,10 @@ class Revision implements IDBAccessObject { 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ), 'rev_deleted' => $this->mDeleted, 'rev_len' => $this->mSize, - 'rev_parent_id' => is_null( $this->mParentId ) + 'rev_parent_id' => $this->mParentId === null ? $this->getPreviousRevisionId( $dbw ) : $this->mParentId, - 'rev_sha1' => is_null( $this->mSha1 ) + 'rev_sha1' => $this->mSha1 === null ? Revision::base36Sha1( $this->mText ) : $this->mSha1, ); @@ -1419,7 +1424,7 @@ class Revision implements IDBAccessObject { $dbw->insert( 'revision', $row, __METHOD__ ); - $this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId(); + $this->mId = $rev_id !== null ? $rev_id : $dbw->insertId(); wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) ); @@ -1508,7 +1513,7 @@ class Revision implements IDBAccessObject { } // If we kept data for lazy extraction, use it now... - if ( isset( $this->mTextRow ) ) { + if ( $this->mTextRow !== null ) { $row = $this->mTextRow; $this->mTextRow = null; } else { diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php index 6a568c2d0c..ec17a0895c 100644 --- a/includes/Sanitizer.php +++ b/includes/Sanitizer.php @@ -383,7 +383,7 @@ class Sanitizer { 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's', 'strike', 'strong', 'tt', 'var', 'div', 'center', 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre', - 'ruby', 'rt', 'rb', 'rp', 'p', 'span', 'abbr', 'dfn', + 'ruby', 'rb', 'rp', 'rt', 'rtc', 'p', 'span', 'abbr', 'dfn', 'kbd', 'samp', 'data', 'time', 'mark' ); $htmlsingle = array( @@ -1685,10 +1685,10 @@ class Sanitizer { # http://www.whatwg.org/html/text-level-semantics.html#the-ruby-element 'ruby' => $common, # rbc - # rtc 'rb' => $common, - 'rt' => $common, #array_merge( $common, array( 'rbspan' ) ), 'rp' => $common, + 'rt' => $common, #array_merge( $common, array( 'rbspan' ) ), + 'rtc' => $common, # MathML root element, where used for extensions # 'title' may not be 100% valid here; it's XHTML diff --git a/includes/Setup.php b/includes/Setup.php index ccb3a154b5..145f55a6d0 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -78,6 +78,14 @@ if ( $wgExtensionAssetsPath === false ) { $wgExtensionAssetsPath = "$wgScriptPath/extensions"; } +// Enable default skins. Temporary, to be removed before 1.24 release. +// This is hacky and bad, the require_once calls should eventually be generated by the installer +// and placed in LocalSettings.php. +// While this is in Setup.php, it needs to be done as soon as possible, as some of the setup code +// depends on all extensions and skins being already required (bug 67318). +require_once "$wgStyleDirectory/MonoBook/MonoBook.php"; +require_once "$wgStyleDirectory/Vector/Vector.php"; + if ( $wgLogo === false ) { $wgLogo = "$wgStylePath/common/images/wiki.png"; } @@ -98,6 +106,10 @@ if ( $wgDeletedDirectory === false ) { $wgDeletedDirectory = "{$wgUploadDirectory}/deleted"; } +if ( $wgGitInfoCacheDirectory === false && $wgCacheDirectory !== false ) { + $wgGitInfoCacheDirectory = "{$wgCacheDirectory}/gitinfo"; +} + if ( isset( $wgFileStore['deleted']['directory'] ) ) { $wgDeletedDirectory = $wgFileStore['deleted']['directory']; } @@ -106,7 +118,7 @@ if ( isset( $wgFooterIcons['copyright'] ) && isset( $wgFooterIcons['copyright']['copyright'] ) && $wgFooterIcons['copyright']['copyright'] === array() ) { - if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) { + if ( $wgCopyrightIcon ) { $wgFooterIcons['copyright']['copyright'] = $wgCopyrightIcon; } elseif ( $wgRightsIcon || $wgRightsText ) { $wgFooterIcons['copyright']['copyright'] = array( @@ -388,6 +400,11 @@ if ( $wgNewUserLog ) { $wgLogActionsHandlers['newusers/autocreate'] = 'NewUsersLogFormatter'; } +if ( $wgPageLanguageUseDB ) { + $wgLogTypes[] = 'pagelang'; + $wgLogActionsHandlers['pagelang/pagelang'] = 'PageLangLogFormatter'; +} + if ( $wgCookieSecure === 'detect' ) { $wgCookieSecure = ( WebRequest::detectProtocol() === 'https' ); } @@ -568,26 +585,33 @@ if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode && wfProfileOut( $fname . '-session' ); wfProfileIn( $fname . '-globals' ); +/** + * @var Language $wgContLang + */ $wgContLang = Language::factory( $wgLanguageCode ); $wgContLang->initEncoding(); $wgContLang->initContLang(); // Now that variant lists may be available... $wgRequest->interpolateTitle(); + +/** + * @var User $wgUser + */ $wgUser = RequestContext::getMain()->getUser(); // BackCompat /** - * @var $wgLang Language + * @var Language $wgLang */ $wgLang = new StubUserLang; /** - * @var OutputPage + * @var OutputPage $wgOut */ $wgOut = RequestContext::getMain()->getOutput(); // BackCompat /** - * @var $wgParser Parser + * @var Parser $wgParser */ $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) ); @@ -596,17 +620,32 @@ if ( !is_object( $wgAuth ) ) { wfRunHooks( 'AuthPluginSetup', array( &$wgAuth ) ); } -# Placeholders in case of DB error +/** + * @var Title $wgTitle + */ $wgTitle = null; $wgDeferredUpdateList = array(); -// Enable default skins. -// This is hacky and bad, but it will go away before 1.24 release (or so I hope). -// These lines should eventually be placed in skins' meta definition files, and loaded by a -// require_once for each skin file generated by the installer and placed in LocalSettings.php. -require_once "$wgStyleDirectory/MonoBook/MonoBook.php"; -require_once "$wgStyleDirectory/Vector/Vector.php"; +// Disable all other email settings automatically if $wgEnableEmail +// is set to false. - bug 63678 +if ( !$wgEnableEmail ) { + $wgAllowHTMLEmail = false; + $wgEmailAuthentication = false; // do not require auth if you're not sending email anyway + $wgEnableUserEmail = false; + $wgEnotifFromEditor = false; + $wgEnotifImpersonal = false; + $wgEnotifMaxRecips = 0; + $wgEnotifMinorEdits = false; + $wgEnotifRevealEditorAddress = false; + $wgEnotifUseJobQ = false; + $wgEnotifUseRealName = false; + $wgEnotifUserTalk = false; + $wgEnotifWatchlist = false; + unset( $wgGroupPermissions['user']['sendemail'] ); + $wgUserEmailUseReplyTo = false; + $wgUsersNotifiedOnAllChanges = array(); +} wfProfileOut( $fname . '-globals' ); wfProfileIn( $fname . '-extensions' ); diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php index b99840fb4b..b877544099 100644 --- a/includes/SiteConfiguration.php +++ b/includes/SiteConfiguration.php @@ -191,7 +191,7 @@ class SiteConfiguration { * @param array $params Array of parameters. * @return mixed The value of the setting requested. */ - protected function getSetting( $settingName, $wiki, /*array*/ $params ) { + protected function getSetting( $settingName, $wiki, array $params ) { $retval = null; if ( array_key_exists( $settingName, $this->settings ) ) { $thisSetting =& $this->settings[$settingName]; @@ -207,14 +207,14 @@ class SiteConfiguration { // Do tag settings foreach ( $params['tags'] as $tag ) { if ( array_key_exists( $tag, $thisSetting ) ) { - if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$tag] ) ) { + if ( is_array( $retval ) && is_array( $thisSetting[$tag] ) ) { $retval = self::arrayMerge( $retval, $thisSetting[$tag] ); } else { $retval = $thisSetting[$tag]; } break 2; } elseif ( array_key_exists( "+$tag", $thisSetting ) && is_array( $thisSetting["+$tag"] ) ) { - if ( !isset( $retval ) ) { + if ( $retval === null ) { $retval = array(); } $retval = self::arrayMerge( $retval, $thisSetting["+$tag"] ); @@ -224,7 +224,7 @@ class SiteConfiguration { $suffix = $params['suffix']; if ( !is_null( $suffix ) ) { if ( array_key_exists( $suffix, $thisSetting ) ) { - if ( isset( $retval ) && is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) { + if ( is_array( $retval ) && is_array( $thisSetting[$suffix] ) ) { $retval = self::arrayMerge( $retval, $thisSetting[$suffix] ); } else { $retval = $thisSetting[$suffix]; @@ -233,7 +233,7 @@ class SiteConfiguration { } elseif ( array_key_exists( "+$suffix", $thisSetting ) && is_array( $thisSetting["+$suffix"] ) ) { - if ( !isset( $retval ) ) { + if ( $retval === null ) { $retval = array(); } $retval = self::arrayMerge( $retval, $thisSetting["+$suffix"] ); @@ -450,7 +450,7 @@ class SiteConfiguration { * @param array $wikiTags The tags assigned to the wiki. * @return array */ - protected function mergeParams( $wiki, $suffix, /*array*/ $params, /*array*/ $wikiTags ) { + protected function mergeParams( $wiki, $suffix, array $params, array $wikiTags ) { $ret = $this->getWikiParams( $wiki ); if ( is_null( $ret['suffix'] ) ) { diff --git a/includes/SiteStats.php b/includes/SiteStats.php index 4e737d136e..e5c1e1728e 100644 --- a/includes/SiteStats.php +++ b/includes/SiteStats.php @@ -273,7 +273,8 @@ class SiteStatsInit { private $db; // Various stats - private $mEdits, $mArticles, $mPages, $mUsers, $mViews, $mFiles = 0; + private $mEdits = null, $mArticles = null, $mPages = null; + private $mUsers = null, $mViews = null, $mFiles = null; /** * Constructor @@ -402,16 +403,17 @@ class SiteStatsInit { } /** - * Refresh site_stats. + * Refresh site_stats. If you want ss_total_views to be updated, be sure to + * call views() first. */ - protected function refresh() { + public function refresh() { $values = array( 'ss_row_id' => 1, - 'ss_total_edits' => $this->mEdits, - 'ss_good_articles' => $this->mArticles, - 'ss_total_pages' => $this->mPages, - 'ss_users' => $this->mUsers, - 'ss_images' => $this->mFiles, + 'ss_total_edits' => ( $this->mEdits === null ? $this->edits() : $this->mEdits ), + 'ss_good_articles' => ( $this->mArticles === null ? $this->articles() : $this->mArticles ), + 'ss_total_pages' => ( $this->mPages === null ? $this->pages() : $this->mPages ), + 'ss_users' => ( $this->mUsers === null ? $this->users() : $this->mUsers ), + 'ss_images' => ( $this->mFiles === null ? $this->files() : $this->mFiles ), ) + ( $this->mViews ? array( 'ss_total_views' => $this->mViews ) : array() ); diff --git a/includes/Skin.php b/includes/Skin.php index 177e2b1df6..bc30eff4f1 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -421,21 +421,6 @@ abstract class Skin extends ContextSource { } } - /** - * Make a " +' + ), + // Load module styles only + // This also tests the order the modules are put into the url + array( + array( array( 'test.baz', 'test.foo', 'test.bar' ), ResourceLoaderModule::TYPE_STYLES ), + ' +' + ), + // Load private module (combined) + array( + array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ), + ' +' + ), + // Load module script with with ESI + array( + array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS, true ), + ' +' + ), + // Load module styles with with ESI + array( + array( 'test.foo', ResourceLoaderModule::TYPE_STYLES, true ), + ' +', + ), + ); + } + + + /** + * @dataProvider provideMakeResourceLoaderLink + * @covers OutputPage::makeResourceLoaderLink + */ + public function testMakeResourceLoaderLink( $args, $expectedHtml) { + $this->setMwGlobals( array( + 'wgResourceLoaderUseESI' => true, + 'wgLoadScript' => 'http://127.0.0.1:8080/w/load.php', + // Affects whether CDATA is inserted + 'wgWellFormedXml' => false, + // Cache key is based on database name, and affects output; + // this test should not touch the database anyways. + 'wgDBname' => 'wiki', + 'wgDBprefix' => '', + ) ); + $class = new ReflectionClass( 'OutputPage' ); + $method = $class->getMethod( 'makeResourceLoaderLink' ); + $method->setAccessible( true ); + $ctx = new RequestContext(); + $out = new OutputPage( $ctx ); + $rl = $out->getResourceLoader(); + $rl->register( array( + 'test.foo' => new ResourceLoaderTestModule(array( + 'script' => 'mw.test.foo( { a: true } );', + 'styles' => '.mw-test-foo { content: "style"; }', + )), + 'test.bar' => new ResourceLoaderTestModule(array( + 'script' => 'mw.test.bar( { a: true } );', + 'styles' => '.mw-test-bar { content: "style"; }', + )), + 'test.baz' => new ResourceLoaderTestModule(array( + 'script' => 'mw.test.baz( { a: true } );', + 'styles' => '.mw-test-baz { content: "style"; }', + )), + 'test.quux' => new ResourceLoaderTestModule(array( + 'script' => 'mw.test.baz( { token: 123 } );', + 'styles' => '/* pref-animate=off */ .mw-icon { transition: none; }', + 'group' => 'private', + )), + ) ); + $links = $method->invokeArgs( $out, $args ); + $this->assertEquals( $expectedHtml, $links['html'] ); + } } diff --git a/tests/phpunit/includes/RequestContextTest.php b/tests/phpunit/includes/RequestContextTest.php index f595d2dc64..cae0e52ef5 100644 --- a/tests/phpunit/includes/RequestContextTest.php +++ b/tests/phpunit/includes/RequestContextTest.php @@ -2,6 +2,7 @@ /** * @group Database + * @group RequestContext */ class RequestContextTest extends MediaWikiTestCase { @@ -53,6 +54,11 @@ class RequestContextTest extends MediaWikiTestCase { 'USER-AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0' ) ); + // importScopedSession() sets these variables + $this->setMwGlobals( array( + 'wgUser' => new User, + 'wgRequest' => new FauxRequest, + ) ); $sc = RequestContext::importScopedSession( $sinfo ); // load new context $info = $context->exportSession(); diff --git a/tests/phpunit/includes/SampleTest.php b/tests/phpunit/includes/SampleTest.php index 758c2e277b..6fdc23911f 100644 --- a/tests/phpunit/includes/SampleTest.php +++ b/tests/phpunit/includes/SampleTest.php @@ -16,6 +16,7 @@ class TestSample extends MediaWikiLangTestCase { $this->setMwGlobals( array( 'wgContLang' => Language::factory( 'en' ), 'wgLanguageCode' => 'en', + 'wgCapitalLinks' => true, ) ); } diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index ac80a9aa37..988a4a4c13 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -41,6 +41,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase { NS_MEDIAWIKI => 'editinterface', ), ) ); + // Without this testUserBlock will use a non-English context on non-English MediaWiki + // installations (because of how Title::checkUserBlock is implemented) and fail. + RequestContext::resetMain(); $this->userName = 'Useruser'; $this->altUserName = 'Altuseruser'; diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index 1c7b6623cd..68715824af 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -87,6 +87,7 @@ class TitleTest extends MediaWikiTestCase { str_repeat( 'x', 252 ), // interwiki prefix 'localtestiw: #anchor', + 'localtestiw:', 'localtestiw:foo', 'localtestiw: foo # anchor', 'localtestiw: Talk: Sandbox # anchor', @@ -147,7 +148,6 @@ class TitleTest extends MediaWikiTestCase { 'Category: ', 'Category: #bar', // interwiki prefix - 'localtestiw:', 'localtestiw: Talk: # anchor', 'localtestiw: Talk:' ) as $text ) { diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index a19d035baf..36de114ead 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -125,6 +125,31 @@ class UserTest extends MediaWikiTestCase { ); } + /** + * @dataProvider provideIPs + * @covers User::isIP + */ + public function testIsIP( $value, $result, $message ) { + $this->assertEquals( $this->user->isIP( $value ), $result, $message ); + } + + public static function provideIPs() { + return array( + array( '', false, 'Empty string' ), + array( ' ', false, 'Blank space' ), + array( '10.0.0.0', true, 'IPv4 private 10/8' ), + array( '10.255.255.255', true, 'IPv4 private 10/8' ), + array( '192.168.1.1', true, 'IPv4 private 192.168/16' ), + array( '203.0.113.0', true, 'IPv4 example' ), + array( '2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff', true, 'IPv6 example' ), + // Not valid IPs but classified as such by MediaWiki for negated asserting + // of whether this might be the identifier of a logged-out user or whether + // to allow usernames like it. + array( '300.300.300.300', true, 'Looks too much like an IPv4 address' ), + array( '203.0.113.xxx', true, 'Assigned by UseMod to cloaked logged-out users' ), + ); + } + /** * @dataProvider provideUserNames * @covers User::isValidUserName @@ -148,6 +173,9 @@ class UserTest extends MediaWikiTestCase { array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ), array( 'ജോസ്‌തോമസ്', false, 'ZWNJ- Format control character' ), array( 'Ab cd', false, ' Ideographic space' ), + array( '300.300.300.300', false, 'Looks too much like an IPv4 address' ), + array( '302.113.311.900', false, 'Looks too much like an IPv4 address' ), + array( '203.0.113.xxx', false, 'Reserved for usage by UseMod for cloaked logged-out users' ), ); } @@ -157,7 +185,7 @@ class UserTest extends MediaWikiTestCase { * Extensions and core */ public function testAllRightsWithMessage() { - //Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights + // Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights $allRights = User::getAllRights(); $allMessageKeys = Language::getMessageKeysFor( 'en' ); diff --git a/tests/phpunit/includes/WikiPageTest.php b/tests/phpunit/includes/WikiPageTest.php index 78457d2d1a..7f7945b8d8 100644 --- a/tests/phpunit/includes/WikiPageTest.php +++ b/tests/phpunit/includes/WikiPageTest.php @@ -499,6 +499,10 @@ class WikiPageTest extends MediaWikiLangTestCase { * @covers WikiPage::getRedirectTarget */ public function testGetRedirectTarget( $title, $model, $text, $target ) { + $this->setMwGlobals( array( + 'wgCapitalLinks' => true, + ) ); + $page = $this->createPage( $title, $text, $model ); # sanity check, because this test seems to fail for no reason for some people. diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index e031ce3337..5f955bbc4e 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -17,7 +17,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { /** @var DerivativeContext */ private $mContext; - private $mOldGetPreferencesHooks = false; + private $mOldGetPreferencesHooks; private static $Success = array( 'options' => 'success' ); @@ -61,10 +61,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase { protected function tearDown() { global $wgHooks; - if ( $this->mOldGetPreferencesHooks !== false ) { - $wgHooks['GetPreferences'] = $this->mOldGetPreferencesHooks; - $this->mOldGetPreferencesHooks = false; - } + $wgHooks['GetPreferences'] = $this->mOldGetPreferencesHooks; + $this->mOldGetPreferencesHooks = false; parent::tearDown(); } diff --git a/tests/phpunit/includes/api/query/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index ea0b3235ab..7b686a3154 100644 --- a/tests/phpunit/includes/api/query/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -36,9 +36,12 @@ class ApiQueryTest extends ApiTestCase { } public function testTitlesGetNormalized() { - global $wgMetaNamespace; + $this->setMwGlobals( array( + 'wgCapitalLinks' => true, + ) ); + $data = $this->doApiRequest( array( 'action' => 'query', 'titles' => 'Project:articleA|article_B' ) ); @@ -97,6 +100,10 @@ class ApiQueryTest extends ApiTestCase { * @dataProvider provideTestTitlePartToKey */ function testTitlePartToKey( $titlePart, $namespace, $expected, $expectException ) { + $this->setMwGlobals( array( + 'wgCapitalLinks' => true, + ) ); + $api = new MockApiQueryBase(); $exceptionCaught = false; try { diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php index 03cbbc0153..2f8110945e 100644 --- a/tests/phpunit/includes/content/TextContentTest.php +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -31,6 +31,7 @@ class TextContentTest extends MediaWikiLangTestCase { ), 'wgUseTidy' => false, 'wgAlwaysUseTidy' => false, + 'wgCapitalLinks' => true, ) ); // bypass hooks that force custom rendering diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 91399beee0..e642177f33 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -80,4 +80,62 @@ class MWDebugTest extends MediaWikiTestCase { "Only one deprecated warning per function should be kept" ); } + + /** + * @covers MWDebug::appendDebugInfoToApiResult + */ + public function testAppendDebugInfoToApiResultXmlFormat() { + $request = $this->newApiRequest( + array( 'action' => 'help', 'format' => 'xml' ), + '/api.php?action=help&format=xml' + ); + + $context = new RequestContext(); + $context->setRequest( $request ); + + $apiMain = new ApiMain( $context ); + + $result = new ApiResult( $apiMain ); + $result->setRawMode( true ); + + MWDebug::appendDebugInfoToApiResult( $context, $result ); + + $this->assertInstanceOf( 'ApiResult', $result ); + $data = $result->getData(); + + $expectedKeys = array( 'mwVersion', 'phpVersion', 'gitRevision', 'gitBranch', + 'gitViewUrl', 'time', 'log', 'debugLog', 'queries', 'request', 'memory', + 'memoryPeak', 'includes', 'profile', '_element' ); + + foreach( $expectedKeys as $expectedKey ) { + $this->assertArrayHasKey( $expectedKey, $data['debuginfo'], "debuginfo has $expectedKey" ); + } + + $xml = ApiFormatXml::recXmlPrint( 'help', $data ); + + // exception not thrown + $this->assertInternalType( 'string', $xml ); + } + + /** + * @param string[] $params + * @param string $requestUrl + * + * @return FauxRequest + */ + private function newApiRequest( array $params, $requestUrl ) { + $request = $this->getMockBuilder( 'FauxRequest' ) + ->setMethods( array( 'getRequestURL' ) ) + ->setConstructorArgs( array( + $params + ) ) + ->getMock(); + + $request->expects( $this->any() ) + ->method( 'getRequestURL' ) + ->will( $this->returnValue( $requestUrl ) ); + + return $request; + } + } diff --git a/tests/phpunit/includes/filerepo/RepoGroupTest.php b/tests/phpunit/includes/filerepo/RepoGroupTest.php new file mode 100644 index 0000000000..2c5233841d --- /dev/null +++ b/tests/phpunit/includes/filerepo/RepoGroupTest.php @@ -0,0 +1,57 @@ +setMWGlobals( 'wgForeignFileRepos', array() ); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + $this->assertFalse( RepoGroup::singleton()->hasForeignRepos() ); + } + + function testHasForeignRepoPositive() { + $this->setUpForeignRepo(); + $this->assertTrue( RepoGroup::singleton()->hasForeignRepos() ); + } + + function testForEachForeignRepo() { + $this->setUpForeignRepo(); + $fakeCallback = $this->getMock( 'RepoGroupTestHelper' ); + $fakeCallback->expects( $this->once() )->method( 'callback' ); + RepoGroup::singleton()->forEachForeignRepo( array( $fakeCallback, 'callback' ), array( array() ) ); + } + + function testForEachForeignRepoNone() { + $this->setMWGlobals( 'wgForeignFileRepos', array() ); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + $fakeCallback = $this->getMock( 'RepoGroupTestHelper' ); + $fakeCallback->expects( $this->never() )->method( 'callback' ); + RepoGroup::singleton()->forEachForeignRepo( array( $fakeCallback, 'callback' ), array( array() ) ); + } + + private function setUpForeignRepo() { + global $wgUploadDirectory; + $this->setMWGlobals( 'wgForeignFileRepos', array( array( + 'class' => 'ForeignAPIRepo', + 'name' => 'wikimediacommons', + 'backend' => 'wikimediacommons-backend', + 'apibase' => 'https://commons.wikimedia.org/w/api.php', + 'hashLevels' => 2, + 'fetchDescription' => true, + 'descriptionCacheExpiry' => 43200, + 'apiThumbCacheExpiry' => 86400, + 'directory' => $wgUploadDirectory + ) ) ); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + } +} + +/** + * Quick helper class to use as a mock callback for RepoGroup::singleton()->forEachForeignRepo. + */ +class RepoGroupTestHelper { + function callback( FileRepo $repo, array $foo ) { + return true; + } +} diff --git a/tests/phpunit/includes/filerepo/file/FileTest.php b/tests/phpunit/includes/filerepo/file/FileTest.php new file mode 100644 index 0000000000..9232ce40d8 --- /dev/null +++ b/tests/phpunit/includes/filerepo/file/FileTest.php @@ -0,0 +1,348 @@ +setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] ); + $this->setMwGlobals( 'wgThumbnailMinimumBucketDistance', $data['minimumBucketDistance'] ); + + $fileMock = $this->getMockBuilder( 'File' ) + ->setConstructorArgs( array( 'fileMock', false ) ) + ->setMethods( array( 'getWidth' ) ) + ->getMockForAbstractClass(); + + $fileMock->expects( $this->any() )->method( 'getWidth' )->will( + $this->returnValue( $data['width'] ) ); + + $this->assertEquals( + $data['expectedBucket'], + $fileMock->getThumbnailBucket( $data['requestedWidth'] ), + $data['message'] ); + } + + public function getThumbnailBucketProvider() { + $defaultBuckets = array( 256, 512, 1024, 2048, 4096 ); + + return array( + array( array( + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 120, + 'expectedBucket' => 256, + 'message' => 'Picking bucket bigger than requested size' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 300, + 'expectedBucket' => 512, + 'message' => 'Picking bucket bigger than requested size' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 1024, + 'expectedBucket' => 2048, + 'message' => 'Picking bucket bigger than requested size' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 2048, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because none is bigger than the requested size' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 3500, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because requested size is bigger than original' + ) ), + array( array( + 'buckets' => array( 1024 ), + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 1024, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because requested size equals biggest bucket' + ) ), + array( array( + 'buckets' => null, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 1024, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because no buckets have been specified' + ) ), + array( array( + 'buckets' => array( 256, 512 ), + 'minimumBucketDistance' => 10, + 'width' => 3000, + 'requestedWidth' => 245, + 'expectedBucket' => 256, + 'message' => 'Requested width is distant enough from next bucket for it to be picked' + ) ), + array( array( + 'buckets' => array( 256, 512 ), + 'minimumBucketDistance' => 10, + 'width' => 3000, + 'requestedWidth' => 246, + 'expectedBucket' => 512, + 'message' => 'Requested width is too close to next bucket, picking next one' + ) ), + ); + } + + /** + * @dataProvider getThumbnailSourceProvider + * @covers File::getThumbnailSource + */ + public function testGetThumbnailSource( $data ) { + $backendMock = $this->getMockBuilder( 'FSFileBackend' ) + ->setConstructorArgs( array( array( 'name' => 'backendMock', 'wikiId' => wfWikiId() ) ) ) + ->getMock(); + + $repoMock = $this->getMockBuilder( 'FileRepo' ) + ->setConstructorArgs( array( array( 'name' => 'repoMock', 'backend' => $backendMock ) ) ) + ->setMethods( array( 'fileExists', 'getLocalReference' ) ) + ->getMock(); + + $fsFile = new FSFile( 'fsFilePath' ); + + $repoMock->expects( $this->any() )->method( 'fileExists' )->will( + $this->returnValue( true ) ); + + $repoMock->expects( $this->any() )->method( 'getLocalReference' )->will( + $this->returnValue( $fsFile ) ); + + $handlerMock = $this->getMock( 'BitmapHandler', array( 'supportsBucketing' ) ); + $handlerMock->expects( $this->any() )->method( 'supportsBucketing' )->will( + $this->returnValue( $data['supportsBucketing'] ) ); + + $fileMock = $this->getMockBuilder( 'File' ) + ->setConstructorArgs( array( 'fileMock', $repoMock ) ) + ->setMethods( array( 'getThumbnailBucket', 'getLocalRefPath', 'getHandler' ) ) + ->getMockForAbstractClass(); + + $fileMock->expects( $this->any() )->method( 'getThumbnailBucket' )->will( + $this->returnValue( $data['thumbnailBucket'] ) ); + + $fileMock->expects( $this->any() )->method( 'getLocalRefPath' )->will( + $this->returnValue( 'localRefPath' ) ); + + $fileMock->expects( $this->any() )->method( 'getHandler' )->will( + $this->returnValue( $handlerMock ) ); + + $reflection = new ReflectionClass( $fileMock ); + $reflection_property = $reflection->getProperty( 'handler' ); + $reflection_property->setAccessible( true ); + $reflection_property->setValue( $fileMock, $handlerMock ); + + if ( !is_null( $data['tmpBucketedThumbCache'] ) ) { + $reflection_property = $reflection->getProperty( 'tmpBucketedThumbCache' ); + $reflection_property->setAccessible( true ); + $reflection_property->setValue( $fileMock, $data['tmpBucketedThumbCache'] ); + } + + $result = $fileMock->getThumbnailSource( + array( 'physicalWidth' => $data['physicalWidth'] ) ); + + $this->assertEquals( $data['expectedPath'], $result['path'], $data['message'] ); + } + + public function getThumbnailSourceProvider() { + return array( + array( array( + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => null, + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => 'fsFilePath', + 'message' => 'Path downloaded from storage' + ) ), + array( array( + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => array( 1024 => '/tmp/shouldnotexist' + rand() ), + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => 'fsFilePath', + 'message' => 'Path downloaded from storage because temp file is missing' + ) ), + array( array( + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => array( 1024 => '/tmp' ), + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => '/tmp', + 'message' => 'Temporary path because temp file was found' + ) ), + array( array( + 'supportsBucketing' => false, + 'tmpBucketedThumbCache' => null, + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => 'localRefPath', + 'message' => 'Original file path because bucketing is unsupported by handler' + ) ), + array( array( + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => null, + 'thumbnailBucket' => false, + 'physicalWidth' => 2048, + 'expectedPath' => 'localRefPath', + 'message' => 'Original file path because no width provided' + ) ), + ); + } + + /** + * @dataProvider generateBucketsIfNeededProvider + * @covers File::generateBucketsIfNeeded + */ + public function testGenerateBucketsIfNeeded( $data ) { + $this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] ); + + $backendMock = $this->getMockBuilder( 'FSFileBackend' ) + ->setConstructorArgs( array( array( 'name' => 'backendMock', 'wikiId' => wfWikiId() ) ) ) + ->getMock(); + + $repoMock = $this->getMockBuilder( 'FileRepo' ) + ->setConstructorArgs( array( array( 'name' => 'repoMock', 'backend' => $backendMock ) ) ) + ->setMethods( array( 'fileExists', 'getLocalReference' ) ) + ->getMock(); + + $fileMock = $this->getMockBuilder( 'File' ) + ->setConstructorArgs( array( 'fileMock', $repoMock ) ) + ->setMethods( array( 'getWidth', 'getBucketThumbPath', 'makeTransformTmpFile', 'generateAndSaveThumb', 'getHandler' ) ) + ->getMockForAbstractClass(); + + $handlerMock = $this->getMock( 'JpegHandler', array( 'supportsBucketing' ) ); + $handlerMock->expects( $this->any() )->method( 'supportsBucketing' )->will( + $this->returnValue( true ) ); + + $fileMock->expects( $this->any() )->method( 'getHandler' )->will( + $this->returnValue( $handlerMock ) ); + + $reflectionMethod = new ReflectionMethod( 'File', 'generateBucketsIfNeeded' ); + $reflectionMethod->setAccessible( true ); + + $fileMock->expects( $this->any() ) + ->method( 'getWidth' ) + ->will( $this->returnValue( $data['width'] ) ); + + $fileMock->expects( $data['expectedGetBucketThumbPathCalls'] ) + ->method( 'getBucketThumbPath' ); + + $repoMock->expects( $data['expectedFileExistsCalls'] ) + ->method( 'fileExists' ) + ->will( $this->returnValue( $data['fileExistsReturn'] ) ); + + $fileMock->expects( $data['expectedMakeTransformTmpFile'] ) + ->method( 'makeTransformTmpFile' ) + ->will( $this->returnValue( $data['makeTransformTmpFileReturn'] ) ); + + $fileMock->expects( $data['expectedGenerateAndSaveThumb'] ) + ->method( 'generateAndSaveThumb' ) + ->will( $this->returnValue( $data['generateAndSaveThumbReturn'] ) ); + + $this->assertEquals( $data['expectedResult'], + $reflectionMethod->invoke( + $fileMock, + array( + 'physicalWidth' => $data['physicalWidth'], + 'physicalHeight' => $data['physicalHeight'] ) + ), + $data['message'] ); + } + + public function generateBucketsIfNeededProvider() { + $defaultBuckets = array( 256, 512, 1024, 2048, 4096 ); + + return array( + array( array( + 'buckets' => $defaultBuckets, + 'width' => 256, + 'physicalWidth' => 256, + 'physicalHeight' => 100, + 'expectedGetBucketThumbPathCalls' => $this->never(), + 'expectedFileExistsCalls' => $this->never(), + 'fileExistsReturn' => null, + 'expectedMakeTransformTmpFile' => $this->never(), + 'makeTransformTmpFileReturn' => false, + 'expectedGenerateAndSaveThumb' => $this->never(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'No bucket found, nothing to generate' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => true, + 'expectedMakeTransformTmpFile' => $this->never(), + 'makeTransformTmpFileReturn' => false, + 'expectedGenerateAndSaveThumb' => $this->never(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'File already exists, no reason to generate buckets' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => false, + 'expectedMakeTransformTmpFile' => $this->once(), + 'makeTransformTmpFileReturn' => false, + 'expectedGenerateAndSaveThumb' => $this->never(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'Cannot generate temp file for bucket' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => false, + 'expectedMakeTransformTmpFile' => $this->once(), + 'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ), + 'expectedGenerateAndSaveThumb' => $this->once(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'Bucket image could not be generated' + ) ), + array( array( + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => false, + 'expectedMakeTransformTmpFile' => $this->once(), + 'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ), + 'expectedGenerateAndSaveThumb' => $this->once(), + 'generateAndSaveThumbReturn' => new ThumbnailImage( false, 'bar', false, false ), + 'expectedResult' => true, + 'message' => 'Bucket image could not be generated' + ) ), + ); + } +} diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index bb5e398c6a..2b4d60d9a0 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -291,6 +291,41 @@ class CSSMinTest extends MediaWikiTestCase { '@import url(//localhost/styles.css?query=yes)', '@import url(//localhost/styles.css?query=yes)', ), + array( + 'Simple case with comments before url', + 'foo { prop: /* some {funny;} comment */ url(bar.png); }', + 'foo { prop: /* some {funny;} comment */ url(http://localhost/w/bar.png); }', + ), + array( + 'Simple case with comments after url', + 'foo { prop: url(red.gif)/* some {funny;} comment */ ; }', + 'foo { prop: url(http://localhost/w/red.gif?timestamp)/* some {funny;} comment */ ; }', + ), + array( + 'Embedded file with comment before url', + 'foo { /* @embed */ background: /* some {funny;} comment */ url(red.gif); }', + "foo { background: /* some {funny;} comment */ url($red); background: /* some {funny;} comment */ url(http://localhost/w/red.gif?timestamp)!ie; }", + ), + array( + 'Embedded file with comments inside and outside the rule', + 'foo { /* @embed */ background: url(red.gif) /* some {foo;} comment */; /* some {bar;} comment */ }', + "foo { background: url($red) /* some {foo;} comment */; background: url(http://localhost/w/red.gif?timestamp) /* some {foo;} comment */!ie; /* some {bar;} comment */ }", + ), + array( + 'Embedded file with comment outside the rule', + 'foo { /* @embed */ background: url(red.gif); /* some {funny;} comment */ }', + "foo { background: url($red); background: url(http://localhost/w/red.gif?timestamp)!ie; /* some {funny;} comment */ }", + ), + array( + 'Rule with two urls, each with comments', + '{ background: /*asd*/ url(something.png); background: /*jkl*/ url(something.png); }', + '{ background: /*asd*/ url(http://localhost/w/something.png); background: /*jkl*/ url(http://localhost/w/something.png); }', + ), + array( + 'Sanity check for offending line from jquery.ui.theme.css (bug 60077)', + '.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }', + '.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(http://localhost/w/images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }', + ), ); } diff --git a/tests/phpunit/includes/libs/MWMessagePackTest.php b/tests/phpunit/includes/libs/MWMessagePackTest.php index b99ef86549..334d5b51d0 100644 --- a/tests/phpunit/includes/libs/MWMessagePackTest.php +++ b/tests/phpunit/includes/libs/MWMessagePackTest.php @@ -14,7 +14,7 @@ class MWMessagePackTest extends MediaWikiTestCase { * serialization function. */ public function provider() { - return array( + $tests = array( array( 'nil', null, 'c0' ), array( 'bool', true, 'c3' ), array( 'bool', false, 'c2' ), @@ -25,16 +25,12 @@ class MWMessagePackTest extends MediaWikiTestCase { array( 'uint 8', 128, 'cc80' ), array( 'uint 16', 1000, 'cd03e8' ), array( 'uint 32', 100000, 'ce000186a0' ), - array( 'uint 64', 10000000000, 'cf00000002540be400' ), array( 'negative fixnum', -1, 'ff' ), array( 'negative fixnum', -2, 'fe' ), array( 'int 8', -128, 'd080' ), array( 'int 8', -35, 'd0dd' ), array( 'int 16', -1000, 'd1fc18' ), array( 'int 32', -100000, 'd2fffe7960' ), - array( 'int 64', -10000000000, 'd3fffffffdabf41c00' ), - array( 'int 64', -223372036854775807, 'd3fce66c50e2840001' ), - array( 'int 64', -9223372036854775807, 'd38000000000000001' ), array( 'double', 0.1, 'cb3fb999999999999a' ), array( 'double', 1.1, 'cb3ff199999999999a' ), array( 'double', 123.456, 'cb405edd2f1a9fbe77' ), @@ -56,6 +52,15 @@ class MWMessagePackTest extends MediaWikiTestCase { '82a36f6e6501a374776f02' ), ); + + if ( PHP_INT_SIZE > 4 ) { + $tests[] = array( 'uint 64', 10000000000, 'cf00000002540be400' ); + $tests[] = array( 'int 64', -10000000000, 'd3fffffffdabf41c00' ); + $tests[] = array( 'int 64', -223372036854775807, 'd3fce66c50e2840001' ); + $tests[] = array( 'int 64', -9223372036854775807, 'd38000000000000001' ); + } + + return $tests; } /** @@ -65,6 +70,6 @@ class MWMessagePackTest extends MediaWikiTestCase { */ public function testPack( $type, $value, $expected ) { $actual = bin2hex( MWMessagePack::pack( $value ) ); - $this->assertEquals( $actual, $expected, $type ); + $this->assertEquals( $expected, $actual, $type ); } } diff --git a/tests/phpunit/includes/media/MediaWikiMediaTestCase.php b/tests/phpunit/includes/media/MediaWikiMediaTestCase.php index 96347d9400..7b64dfde24 100644 --- a/tests/phpunit/includes/media/MediaWikiMediaTestCase.php +++ b/tests/phpunit/includes/media/MediaWikiMediaTestCase.php @@ -29,11 +29,18 @@ abstract class MediaWikiMediaTestCase extends MediaWikiTestCase { 'wikiId' => wfWikiId(), 'containerPaths' => $containers ) ); - $this->repo = new FSRepo( array( + $this->repo = new FSRepo( $this->getRepoOptions() ); + } + + /** + * @return Array Argument for FSRepo constructor + */ + protected function getRepoOptions() { + return array( 'name' => 'temp', 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend - ) ); + ); } /** diff --git a/tests/phpunit/includes/media/XCFTest.php b/tests/phpunit/includes/media/XCFTest.php new file mode 100644 index 0000000000..ae4fa8bc22 --- /dev/null +++ b/tests/phpunit/includes/media/XCFTest.php @@ -0,0 +1,74 @@ +handler = new XCFHandler(); + } + + + /** + * @param string $filename + * @param int $expectedWidth width + * @param int $expectedHeigh height + * @dataProvider provideGetImageSize + * @covers XCFHandler::getImageSize + */ + public function testGetImageSize( $filename, $expectedWidth, $expectedHeight ) { + $file = $this->dataFile( $filename, 'image/x-xcf' ); + $actual = $this->handler->getImageSize( $file, $file->getLocalRefPath() ); + $this->assertEquals( $expectedWidth, $actual[0] ); + $this->assertEquals( $expectedHeight, $actual[1] ); + } + + public static function provideGetImageSize() { + return array( + array( '80x60-2layers.xcf', 80, 60 ), + array( '80x60-RGB.xcf', 80, 60 ), + array( '80x60-Greyscale.xcf', 80, 60 ), + ); + } + + /** + * @param string $metadata Serialized metadata + * @param int $expected One of the class constants of XCFHandler + * @dataProvider provideIsMetadataValid + * @covers XCFHandler::isMetadataValid + */ + public function testIsMetadataValid( $metadata, $expected ) { + $actual = $this->handler->isMetadataValid( null, $metadata ); + $this->assertEquals( $expected, $actual ); + } + + public static function provideIsMetadataValid() { + return array( + array( '', XCFHandler::METADATA_BAD ), + array( serialize( array( 'error' => true ) ), XCFHandler::METADATA_GOOD ), + array( false, XCFHandler::METADATA_BAD ), + array( serialize( array( 'colorType' => 'greyscale-alpha' ) ), XCFHandler::METADATA_GOOD ), + ); + } + + /** + * @param string $filename + * @param string $expected Serialized array + * @dataProvider provideGetMetadata + * @covers XCFHandler::getMetadata + */ + public function testGetMetadata( $filename, $expected ) { + $file = $this->dataFile( $filename, 'image/png' ); + $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" ); + $this->assertEquals( $expected, $actual ); + } + + public static function provideGetMetadata() { + return array( + array( '80x60-2layers.xcf', 'a:1:{s:9:"colorType";s:16:"truecolour-alpha";}' ), + array( '80x60-RGB.xcf', 'a:1:{s:9:"colorType";s:16:"truecolour-alpha";}' ), + array( '80x60-Greyscale.xcf', 'a:1:{s:9:"colorType";s:15:"greyscale-alpha";}' ), + ); + } +} diff --git a/tests/phpunit/includes/normal/CleanUpTest.php b/tests/phpunit/includes/normal/CleanUpTest.php index dff31c1e5a..97b76fe992 100644 --- a/tests/phpunit/includes/normal/CleanUpTest.php +++ b/tests/phpunit/includes/normal/CleanUpTest.php @@ -110,15 +110,20 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - public function testAllBytes() { - $this->doTestBytes( '', '' ); - $this->doTestBytes( 'x', '' ); - $this->doTestBytes( '', 'x' ); - $this->doTestBytes( 'x', 'x' ); + public function provideAllBytes() { + return array( + array( '', '' ), + array( 'x', '' ), + array( '', 'x' ), + array( 'x', 'x' ), + ); } - /** @todo document */ - function doTestBytes( $head, $tail ) { + /** + * @dataProvider provideAllBytes + * @todo document + */ + function testBytes( $head, $tail ) { for ( $i = 0x0; $i < 256; $i++ ) { $char = $head . chr( $i ) . $tail; $clean = UtfNormal::cleanUp( $char ); @@ -149,18 +154,11 @@ class CleanUpTest extends MediaWikiTestCase { } } - /** @todo document */ - public function testDoubleBytes() { - $this->doTestDoubleBytes( '', '' ); - $this->doTestDoubleBytes( 'x', '' ); - $this->doTestDoubleBytes( '', 'x' ); - $this->doTestDoubleBytes( 'x', 'x' ); - } - /** + * @dataProvider provideAllBytes * @todo document */ - function doTestDoubleBytes( $head, $tail ) { + function testDoubleBytes( $head, $tail ) { for ( $first = 0xc0; $first < 0x100; $first += 2 ) { for ( $second = 0x80; $second < 0x100; $second += 2 ) { $char = $head . chr( $first ) . chr( $second ) . $tail; @@ -202,16 +200,11 @@ class CleanUpTest extends MediaWikiTestCase { } } - /** @todo document */ - public function testTripleBytes() { - $this->doTestTripleBytes( '', '' ); - $this->doTestTripleBytes( 'x', '' ); - $this->doTestTripleBytes( '', 'x' ); - $this->doTestTripleBytes( 'x', 'x' ); - } - - /** @todo document */ - function doTestTripleBytes( $head, $tail ) { + /** + * @dataProvider provideAllBytes + * @todo document + */ + function testTripleBytes( $head, $tail ) { for ( $first = 0xc0; $first < 0x100; $first += 2 ) { for ( $second = 0x80; $second < 0x100; $second += 2 ) { #for( $third = 0x80; $third < 0x100; $third++ ) { diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index b81c973ae6..d36697eb15 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -155,26 +155,6 @@ class MagicVariableTest extends MediaWikiTestCase { $this->assertUnPadded( 'revisionmonth1', $month ); } - /** - * Rough tests for {{SERVERNAME}} magic word - * Bug 31176 - * @group Database - * @dataProvider provideDataServernameFromDifferentProtocols - */ - public function testServernameFromDifferentProtocols( $server ) { - $this->setMwGlobals( 'wgServer', $server ); - - $this->assertMagic( 'localhost', 'servername' ); - } - - public static function provideDataServernameFromDifferentProtocols() { - return array( - array( 'http://localhost/' ), - array( 'https://localhost/' ), - array( '//localhost/' ), # bug 31176 - ); - } - ############### HELPERS ############################################ /** assertion helper expecting a magic output which is zero padded */ diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 5c42faecb0..4c72d1c9e4 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -148,6 +148,10 @@ class NewParserTest extends MediaWikiTestCase { # proper precedence when resolving links. (bug 51680) $tmpGlobals['wgExtraNamespaces'] = array( 100 => 'MemoryAlpha' ); + # "extra language links" + # see https://gerrit.wikimedia.org/r/111390 + $tmpGlobals['wgExtraInterlanguageLinkPrefixes'] = array( 'mul' ); + //DjVu support $this->djVuSupport = new DjVuSupport(); diff --git a/tests/phpunit/includes/site/MediaWikiSiteTest.php b/tests/phpunit/includes/site/MediaWikiSiteTest.php index 15b88658d7..c3fd155743 100644 --- a/tests/phpunit/includes/site/MediaWikiSiteTest.php +++ b/tests/phpunit/includes/site/MediaWikiSiteTest.php @@ -32,6 +32,10 @@ class MediaWikiSiteTest extends SiteTest { public function testNormalizePageTitle() { + $this->setMwGlobals( array( + 'wgCapitalLinks' => true, + ) ); + $site = new MediaWikiSite(); $site->setGlobalId( 'enwiki' ); diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php index ea2d28cc01..4f6c4116aa 100644 --- a/tests/phpunit/includes/specials/SpecialPreferencesTest.php +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -51,7 +51,7 @@ class SpecialPreferencesTest extends MediaWikiTestCase { # Do the call, should not spurt a fatal error. $special = new SpecialPreferences(); $special->setContext( $context ); - $special->execute( array() ); + $this->assertNull( $special->execute( array() ) ); } } diff --git a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php index 73d7ff9572..358b0fe2af 100644 --- a/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php +++ b/tests/phpunit/includes/title/MediaWikiPageLinkRendererTest.php @@ -27,6 +27,14 @@ */ class MediaWikiPageLinkRendererTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), + ) ); + } + /** * Returns a mock GenderCache that will return "female" always. * diff --git a/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php b/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php index 1e5aca9129..2cf86636c4 100644 --- a/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php +++ b/tests/phpunit/includes/title/MediaWikiTitleCodecTest.php @@ -40,6 +40,7 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase { 'wgAllowUserJs' => false, 'wgDefaultLanguageVariant' => false, 'wgLocalInterwikis' => array( 'localtestiw' ), + 'wgCapitalLinks' => true, // NOTE: this is why global state is evil. // TODO: refactor access to the interwiki codes so it can be injected. diff --git a/tests/phpunit/languages/LanguageRuTest.php b/tests/phpunit/languages/LanguageRuTest.php index 792cf726da..f64fc722a1 100644 --- a/tests/phpunit/languages/LanguageRuTest.php +++ b/tests/phpunit/languages/LanguageRuTest.php @@ -95,6 +95,16 @@ class LanguageRuTest extends LanguageClassesTestCase { 'Викитека', 'prepositional', ), + array( + 'Викисклада', + 'Викисклад', + 'genitive', + ), + array( + 'Викискладе', + 'Викисклад', + 'prepositional', + ), array( 'Викиданных', 'Викиданные', diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js index ab9aab19d3..50e89da8bc 100644 --- a/tests/qunit/data/testrunner.js +++ b/tests/qunit/data/testrunner.js @@ -136,6 +136,12 @@ return true; } + // Don't iterate over the module registry (the 'script' references would + // be listed as untested methods otherwise) + if ( val === mw.loader.moduleRegistry ) { + return true; + } + return false; }; diff --git a/tests/qunit/suites/resources/jquery/jquery.client.test.js b/tests/qunit/suites/resources/jquery/jquery.client.test.js index df800bc251..c6dd91c4c4 100644 --- a/tests/qunit/suites/resources/jquery/jquery.client.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.client.test.js @@ -4,7 +4,6 @@ var uacount = 0, // Object keyed by userAgent. Value is an array (human-readable name, client-profile object, navigator.platform value) - // Info based on results from http://toolserver.org/~krinkle/testswarm/job/174/ uas = { // Internet Explorer 6 // Internet Explorer 7 diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index 9ca434f1ec..3bfabe4c23 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -258,6 +258,18 @@ expected: 'доводах', description: 'Grammar test for prepositional case, доводы -> доводах' }, + { + word: 'Викисклад', + grammarForm: 'prepositional', + expected: 'Викискладе', + description: 'Grammar test for prepositional case, Викисклад -> Викискладе' + }, + { + word: 'Викисклад', + grammarForm: 'genitive', + expected: 'Викисклада', + description: 'Grammar test for genitive case, Викисклад -> Викисклада' + }, { word: 'песчаник', grammarForm: 'prepositional', diff --git a/thumb.php b/thumb.php index d7bf453382..c0042c2c95 100644 --- a/thumb.php +++ b/thumb.php @@ -367,7 +367,7 @@ function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath global $wgMemc, $wgAttemptFailureEpoch; $key = wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch, - $file->getRepo()->getName(), md5( $file->getName() ), md5( $thumbName ) ); + $file->getRepo()->getName(), $file->getSha1(), md5( $thumbName ) ); // Check if this file keeps failing to render if ( $wgMemc->get( $key ) >= 4 ) { @@ -387,9 +387,17 @@ function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath $thumb = false; $errorHtml = false; + // guard thumbnail rendering with PoolCounter to avoid stampedes + // expensive files use a separate PoolCounter config so it is possible to set up a global limit on them + if ( $file->isExpensiveToThumbnail() ) { + $poolCounterType = 'FileRenderExpensive'; + } else { + $poolCounterType = 'FileRender'; + } + // Thumbnail isn't already there, so create the new thumbnail... try { - $work = new PoolCounterWorkViaCallback( 'FileRender', sha1( $file->getName() ), + $work = new PoolCounterWorkViaCallback( $poolCounterType, sha1( $file->getName() ), array( 'doWork' => function() use ( $file, $params ) { return $file->transform( $params, File::RENDER_NOW );