From: jenkins-bot Date: Thu, 11 Feb 2016 04:15:14 +0000 (+0000) Subject: Merge "Set context on RedirectSpecialPage in MediaWiki.php" X-Git-Tag: 1.31.0-rc.0~7984 X-Git-Url: https://git.heureux-cyclage.org/index.php?a=commitdiff_plain;h=4b63ca7113ee48b8c33ad19abc5b89d452b3590e;hp=4f2fe642839636d0dc8c5699b4e44796ea3890ab;p=lhc%2Fweb%2Fwiklou.git Merge "Set context on RedirectSpecialPage in MediaWiki.php" --- diff --git a/.jshintrc b/.jshintrc index b776e8f21a..62b9d82314 100644 --- a/.jshintrc +++ b/.jshintrc @@ -2,14 +2,15 @@ // Enforcing "bitwise": true, "eqeqeq": true, - "es3": true, + "esversion": 3, "freeze": true, - "latedef": true, + "futurehostile": true, + "latedef": "nofunc", "noarg": true, "nonew": true, + "strict": false, "undef": true, "unused": true, - "strict": false, // Relaxing "laxbreak": true, diff --git a/.travis.yml b/.travis.yml index 2d07596ee5..9062194628 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,9 @@ matrix: fast_finish: true include: - env: dbtype=mysql - php: 5.3 + php: 5.5 - env: dbtype=postgres - php: 5.3 + php: 5.5 - env: dbtype=mysql php: hhvm - env: dbtype=mysql diff --git a/INSTALL b/INSTALL index 2054a57e83..4651a0c7bf 100644 --- a/INSTALL +++ b/INSTALL @@ -6,7 +6,7 @@ Starting with MediaWiki 1.2.0, it's possible to install and configure the wiki "in-place", as long as you have the necessary prerequisites available. Required software: -* Web server with PHP 5.3.3 or higher. +* Web server with PHP 5.5.9 or higher. * A SQL server, the following types are supported ** MySQL 5.0.3 or higher ** PostgreSQL 8.3 or higher diff --git a/RELEASE-NOTES-1.27 b/RELEASE-NOTES-1.27 index f4e4815c29..6e2ca154c7 100644 --- a/RELEASE-NOTES-1.27 +++ b/RELEASE-NOTES-1.27 @@ -8,6 +8,10 @@ THIS IS NOT A RELEASE YET MediaWiki 1.27 is an alpha-quality branch and is not recommended for use in production. +=== PHP version requirement === +As of 1.27, MediaWiki now requires PHP 5.5.9 or higher. This corresponds with +HHVM 3.1. + === Configuration changes in 1.27 === * $wgUseLinkNamespaceDBFields was removed. * Deprecated $wgResourceLoaderMinifierStatementsOnOwnLine and @@ -62,9 +66,41 @@ production. $wgSharedDB and $wgSharedTables are properly set even on the "central" wiki that all others are sharing from and that $wgLocalDatabases is set to the full list of sharing wikis on all those wikis. +* Massive overhaul to session handling: +** $wgSessionsInObjectCache is no longer supported and must be true, due to + MediaWiki\Session\SessionManager. $wgSessionHandler is similarly no longer + used. +** ObjectCacheSessionHandler is removed, replaced with + MediaWiki\Session\PhpSessionHandler. +** PHP session handling in general ($_SESSION, session_id(), and so on) is + deprecated. Use MediaWiki\Session\SessionManager instead. A new config + variable, $wgPHPSessionHandling, is available to cause use of $_SESSION to + issue a deprecation warning or to cause most PHP session handling to throw + exceptions. +** Deprecated UserSetCookies hook. Session-handling extensions should generally + be creating a custom subclass of CookieSessionProvider. Other extensions + messing with cookies can no longer count on user data being saved in cookies + versus other methods. +** Deprecated UserLoadFromSession hook, extensions should create a + MediaWiki\Session\SessionProvider. +** The User cannot be loaded from session until after Setup.php completes. + Attempts to do so will be ignored and the User will remain unloaded. +** CSRF tokens may be fetched from the MediaWiki\Session\Session, which uses + the MediaWiki\Session\Token class. +* MediaWiki will now auto-create users as necessary, removing the need for + extensions to do so. An 'autocreateaccount' right is added to allow + auto-creation when 'createaccount' is not granted to all users. +* Deprecated AuthPluginAutoCreate hook in favor of LocalUserCreated. +* Most cookie-handling methods in User are deprecated. * $wgAllowAsyncCopyUploads and $CopyUploadAsyncTimeout were removed. This was an experimental feature that has never worked. +* Login and createaccount tokens now vary by timestamp. +* LoginForm::getLoginToken() and LoginForm::getCreateaccountToken() + return a MediaWiki\Session\Token, and tokens must be checked using that + class's methods. * $wgEnotifUseJobQ was removed and the job queue is always used. +* The functionality of the ApiSandbox extension has been merged into core. The + extension should no longer be used. === New features in 1.27 === * $wgDataCenterUpdateStickTTL was also added. This decides how long a user @@ -108,6 +144,10 @@ production. * It is now possible to patrol file uploads (both for new files and new versions of existing files). Special:NewFiles has gained an option to filter by patrol status. This functionality can be disabled using $wgUseFilePatrol. +* MediaWiki\Session infrastructure allows for easier use of session mechanisms + other than the usual cookies. +** SessionMetadata and SessionCheckInfo hooks allow for setting and checking + custom session metadata. * Added MWGrants and associated configuration settings $wgGrantPermissions and $wgGrantPermissionGroups to hold configuration for authentication features such as OAuth that want to allow restricting the user rights a user may make @@ -117,10 +157,18 @@ production. $wgMWOAuthGrantPermissionGroups. * Added MWRestrictions as a class to check restrictions on a WebRequest, e.g. to assert that the request comes from a particular IP range. +* Added bot passwords, a rights-restricted login mechanism for API-using bots. * Whitelisted the following HTML attributes for all elements in wikitext: aria-describedby, aria-flowto, aria-label, aria-labelledby, aria-owns. * Removed "presentation" restriction on the HTML role attribute in wikitext. All values are now allowed for the role attribute. +* $wgContentHandlers now also supports callbacks to create an instance of the + appropriate ContentHandler subclass. +* Added $wgAuthenticationTokenVersion, which if non-null prevents the + user_token database field from being exposed in cookies. Setting this would + be a good idea, but will log out all current sessions. +* $wgEventRelayerConfig was added, for managing PubSub event relay configuration, + specifically for reliable CDN url purges. === External library changes in 1.27 === @@ -134,6 +182,7 @@ production. * Added wikimedia/cldr-plural-rule-parser v1.0.0. * Added wikimedia/relpath v1.0.3. * Added wikimedia/running-stat v1.1.0. +* Added wikimedia/php-session-serializer v1.0.3. ==== Removed and replaced external libraries ==== @@ -156,6 +205,9 @@ production. * The following response properties from action=login are deprecated, and may be removed in the future: lgtoken, cookieprefix, sessionid. Clients should handle cookies to properly manage session state. +* action=login transparently allows login using bot passwords. Clients should + merely need to change the username and password used after setting up a bot + password. * action=upload no longer understands statuskey, asyncdownload or leavemessage. === Action API internal changes in 1.27 === @@ -271,7 +323,7 @@ changes to languages because of Phabricator reports. == Compatibility == -MediaWiki 1.27 requires PHP 5.3.3 or later. There is experimental support for +MediaWiki 1.27 requires PHP 5.5.9 or later. There is experimental support for HHVM 3.6.5 or later. MySQL is the recommended DBMS. PostgreSQL or SQLite can also be used, but diff --git a/autoload.php b/autoload.php index 8720186b69..d6e40777ff 100644 --- a/autoload.php +++ b/autoload.php @@ -52,6 +52,7 @@ $wgAutoloadLocalClasses = array( 'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php', 'ApiMain' => __DIR__ . '/includes/api/ApiMain.php', 'ApiManageTags' => __DIR__ . '/includes/api/ApiManageTags.php', + 'ApiMergeHistory' => __DIR__ . '/includes/api/ApiMergeHistory.php', 'ApiMessage' => __DIR__ . '/includes/api/ApiMessage.php', 'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php', 'ApiMove' => __DIR__ . '/includes/api/ApiMove.php', @@ -181,6 +182,7 @@ $wgAutoloadLocalClasses = array( 'BlockListPager' => __DIR__ . '/includes/specials/SpecialBlockList.php', 'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php', 'BmpHandler' => __DIR__ . '/includes/media/BMP.php', + 'BotPassword' => __DIR__ . '/includes/user/BotPassword.php', 'BrokenRedirectsPage' => __DIR__ . '/includes/specials/SpecialBrokenRedirects.php', 'BufferingStatsdDataFactory' => __DIR__ . '/includes/libs/BufferingStatsdDataFactory.php', 'CLIParser' => __DIR__ . '/maintenance/parse.php', @@ -189,6 +191,7 @@ $wgAutoloadLocalClasses = array( 'CacheHelper' => __DIR__ . '/includes/cache/CacheHelper.php', 'CacheTime' => __DIR__ . '/includes/parser/CacheTime.php', 'CachedAction' => __DIR__ . '/includes/actions/CachedAction.php', + 'CachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/CachedBagOStuff.php', 'CachingSiteStore' => __DIR__ . '/includes/site/CachingSiteStore.php', 'CapsCleanup' => __DIR__ . '/maintenance/cleanupCaps.php', 'Category' => __DIR__ . '/includes/Category.php', @@ -392,6 +395,7 @@ $wgAutoloadLocalClasses = array( 'EraseArchivedFile' => __DIR__ . '/maintenance/eraseArchivedFile.php', 'ErrorPageError' => __DIR__ . '/includes/exception/ErrorPageError.php', 'EventRelayer' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php', + 'EventRelayerGroup' => __DIR__ . '/includes/EventRelayerGroup.php', 'EventRelayerMCRD' => __DIR__ . '/includes/libs/eventrelayer/EventRelayerMCRD.php', 'EventRelayerNull' => __DIR__ . '/includes/libs/eventrelayer/EventRelayer.php', 'Exif' => __DIR__ . '/includes/media/Exif.php', @@ -559,6 +563,7 @@ $wgAutoloadLocalClasses = array( 'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php', 'IPTC' => __DIR__ . '/includes/media/IPTC.php', 'IRCColourfulRCFeedFormatter' => __DIR__ . '/includes/rcfeed/IRCColourfulRCFeedFormatter.php', + 'LinkTarget' => __DIR__ . '/includes/LinkTarget.php', 'IcuCollation' => __DIR__ . '/includes/Collation.php', 'IdentityCollation' => __DIR__ . '/includes/Collation.php', 'ImageBuilder' => __DIR__ . '/maintenance/rebuildImages.php', @@ -566,8 +571,8 @@ $wgAutoloadLocalClasses = array( 'ImageGallery' => __DIR__ . '/includes/gallery/TraditionalImageGallery.php', 'ImageGalleryBase' => __DIR__ . '/includes/gallery/ImageGalleryBase.php', 'ImageHandler' => __DIR__ . '/includes/media/ImageHandler.php', - 'ImageHistoryList' => __DIR__ . '/includes/page/ImagePage.php', - 'ImageHistoryPseudoPager' => __DIR__ . '/includes/page/ImagePage.php', + 'ImageHistoryList' => __DIR__ . '/includes/page/ImageHistoryList.php', + 'ImageHistoryPseudoPager' => __DIR__ . '/includes/page/ImageHistoryPseudoPager.php', 'ImageListPager' => __DIR__ . '/includes/specials/SpecialListfiles.php', 'ImagePage' => __DIR__ . '/includes/page/ImagePage.php', 'ImageQueryPage' => __DIR__ . '/includes/specialpage/ImageQueryPage.php', @@ -588,6 +593,7 @@ $wgAutoloadLocalClasses = array( 'InstallDocFormatter' => __DIR__ . '/includes/installer/InstallDocFormatter.php', 'Installer' => __DIR__ . '/includes/installer/Installer.php', 'InstallerOverrides' => __DIR__ . '/mw-config/overrides.php', + 'InstallerSessionProvider' => __DIR__ . '/includes/installer/InstallerSessionProvider.php', 'Interwiki' => __DIR__ . '/includes/interwiki/Interwiki.php', 'InvalidPassword' => __DIR__ . '/includes/password/InvalidPassword.php', 'IteratorDecorator' => __DIR__ . '/includes/utils/iterators/IteratorDecorator.php', @@ -720,6 +726,7 @@ $wgAutoloadLocalClasses = array( 'LogFormatter' => __DIR__ . '/includes/logging/LogFormatter.php', 'LogPage' => __DIR__ . '/includes/logging/LogPage.php', 'LogPager' => __DIR__ . '/includes/logging/LogPager.php', + 'LoggedOutEditToken' => __DIR__ . '/includes/user/LoggedOutEditToken.php', 'LoggedUpdateMaintenance' => __DIR__ . '/maintenance/Maintenance.php', 'LoginForm' => __DIR__ . '/includes/specials/SpecialUserlogin.php', 'LonelyPagesPage' => __DIR__ . '/includes/specials/SpecialLonelypages.php', @@ -786,6 +793,20 @@ $wgAutoloadLocalClasses = array( 'MediaWiki\\Logger\\Monolog\\WikiProcessor' => __DIR__ . '/includes/debug/logger/monolog/WikiProcessor.php', 'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php', 'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php', + 'MediaWiki\\Session\\BotPasswordSessionProvider' => __DIR__ . '/includes/session/BotPasswordSessionProvider.php', + 'MediaWiki\\Session\\CookieSessionProvider' => __DIR__ . '/includes/session/CookieSessionProvider.php', + 'MediaWiki\\Session\\ImmutableSessionProviderWithCookie' => __DIR__ . '/includes/session/ImmutableSessionProviderWithCookie.php', + 'MediaWiki\\Session\\PHPSessionHandler' => __DIR__ . '/includes/session/PHPSessionHandler.php', + 'MediaWiki\\Session\\Session' => __DIR__ . '/includes/session/Session.php', + 'MediaWiki\\Session\\SessionBackend' => __DIR__ . '/includes/session/SessionBackend.php', + 'MediaWiki\\Session\\SessionId' => __DIR__ . '/includes/session/SessionId.php', + 'MediaWiki\\Session\\SessionInfo' => __DIR__ . '/includes/session/SessionInfo.php', + 'MediaWiki\\Session\\SessionManager' => __DIR__ . '/includes/session/SessionManager.php', + 'MediaWiki\\Session\\SessionManagerInterface' => __DIR__ . '/includes/session/SessionManagerInterface.php', + 'MediaWiki\\Session\\SessionProvider' => __DIR__ . '/includes/session/SessionProvider.php', + 'MediaWiki\\Session\\SessionProviderInterface' => __DIR__ . '/includes/session/SessionProviderInterface.php', + 'MediaWiki\\Session\\Token' => __DIR__ . '/includes/session/Token.php', + 'MediaWiki\\Session\\UserInfo' => __DIR__ . '/includes/session/UserInfo.php', 'MediaWiki\\Site\\MediaWikiPageNameNormalizer' => __DIR__ . '/includes/site/MediaWikiPageNameNormalizer.php', 'MediaWiki\\Tidy\\Html5Depurate' => __DIR__ . '/includes/tidy/Html5Depurate.php', 'MediaWiki\\Tidy\\RaggettBase' => __DIR__ . '/includes/tidy/RaggettBase.php', @@ -807,6 +828,7 @@ $wgAutoloadLocalClasses = array( 'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php', 'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php', 'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php', + 'MergeHistory' => __DIR__ . '/includes/MergeHistory.php', 'MergeHistoryPager' => __DIR__ . '/includes/specials/SpecialMergeHistory.php', 'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php', 'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php', @@ -870,7 +892,6 @@ $wgAutoloadLocalClasses = array( 'ORAField' => __DIR__ . '/includes/db/DatabaseOracle.php', 'ORAResult' => __DIR__ . '/includes/db/DatabaseOracle.php', 'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php', - 'ObjectCacheSessionHandler' => __DIR__ . '/includes/objectcache/ObjectCacheSessionHandler.php', 'ObjectFactory' => __DIR__ . '/includes/libs/ObjectFactory.php', 'ObjectFileCache' => __DIR__ . '/includes/cache/ObjectFileCache.php', 'OldChangesList' => __DIR__ . '/includes/changes/OldChangesList.php', @@ -1119,6 +1140,8 @@ $wgAutoloadLocalClasses = array( 'SearchResult' => __DIR__ . '/includes/search/SearchResult.php', 'SearchResultSet' => __DIR__ . '/includes/search/SearchResultSet.php', 'SearchSqlite' => __DIR__ . '/includes/search/SearchSqlite.php', + 'SearchSuggestion' => __DIR__ . '/includes/search/SearchSuggestion.php', + 'SearchSuggestionSet' => __DIR__ . '/includes/search/SearchSuggestionSet.php', 'SearchUpdate' => __DIR__ . '/includes/deferred/SearchUpdate.php', 'SectionProfileCallback' => __DIR__ . '/includes/profiler/SectionProfiler.php', 'SectionProfiler' => __DIR__ . '/includes/profiler/SectionProfiler.php', @@ -1152,10 +1175,12 @@ $wgAutoloadLocalClasses = array( 'SpecialAllMyUploads' => __DIR__ . '/includes/specials/SpecialMyRedirectPages.php', 'SpecialAllPages' => __DIR__ . '/includes/specials/SpecialAllPages.php', 'SpecialApiHelp' => __DIR__ . '/includes/specials/SpecialApiHelp.php', + 'SpecialApiSandbox' => __DIR__ . '/includes/specials/SpecialApiSandbox.php', 'SpecialBlankpage' => __DIR__ . '/includes/specials/SpecialBlankpage.php', 'SpecialBlock' => __DIR__ . '/includes/specials/SpecialBlock.php', 'SpecialBlockList' => __DIR__ . '/includes/specials/SpecialBlockList.php', 'SpecialBookSources' => __DIR__ . '/includes/specials/SpecialBooksources.php', + 'SpecialBotPasswords' => __DIR__ . '/includes/specials/SpecialBotPasswords.php', 'SpecialCachedPage' => __DIR__ . '/includes/specials/SpecialCachedPage.php', 'SpecialCategories' => __DIR__ . '/includes/specials/SpecialCategories.php', 'SpecialChangeContentModel' => __DIR__ . '/includes/specials/SpecialChangeContentModel.php', diff --git a/composer.json b/composer.json index 9ff39ade19..0b50c2a6dd 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "ext-iconv": "*", "liuggio/statsd-php-client": "1.0.18", "mediawiki/at-ease": "1.1.0", - "oojs/oojs-ui": "0.15.1", - "oyejorge/less.php": "1.7.0.9", - "php": ">=5.3.3", + "oojs/oojs-ui": "0.15.3", + "oyejorge/less.php": "1.7.0.10", + "php": ">=5.5.9", "psr/log": "1.0.0", "wikimedia/assert": "0.2.2", "wikimedia/base-convert": "1.0.1", @@ -31,6 +31,7 @@ "wikimedia/cldr-plural-rule-parser": "1.0.0", "wikimedia/composer-merge-plugin": "1.3.0", "wikimedia/ip-set": "1.0.1", + "wikimedia/php-session-serializer": "1.0.3", "wikimedia/relpath": "1.0.3", "wikimedia/running-stat": "1.1.0", "wikimedia/utfnormal": "1.0.3", diff --git a/docs/contenthandler.txt b/docs/contenthandler.txt index 5f9a0b039e..f1f478ef31 100644 --- a/docs/contenthandler.txt +++ b/docs/contenthandler.txt @@ -148,7 +148,8 @@ using a model or format different from the default will result in an error. There are some new globals that can be used to control the behavior of the ContentHandler facility: -* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses. +* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses + or callbacks that create an instance of the appropriate ContentHandler subclass. * $wgNamespaceContentModels maps namespace IDs to a content model that should be the default for that namespace. diff --git a/docs/hooks.txt b/docs/hooks.txt index 24eb868f36..0fe888fe7e 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -513,7 +513,8 @@ sites statistics information. 'ApiQueryTokensRegisterTypes': Use this hook to add additional token types to action=query&meta=tokens. Note that most modules will probably be able to use the 'csrf' token instead of creating their own token types. -&$salts: array( type => salt to pass to User::getEditToken() ) +&$salts: array( type => salt to pass to User::getEditToken() or array of salt + and key to pass to Session::getToken() ) 'APIQueryUsersTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead. Use this hook to add custom token to list=users. Every token has an action, @@ -741,8 +742,9 @@ viewing. redirect was followed. &$article: target article (object) -'AuthPluginAutoCreate': Called when creating a local account for an user logged -in from an external authentication method. +'AuthPluginAutoCreate': DEPRECATED! Use the 'LocalUserCreated' hook instead. +Called when creating a local account for an user logged in from an external +authentication method. $user: User object created locally 'AuthPluginSetup': Update or replace authentication plugin object ($wgAuth). @@ -2584,6 +2586,20 @@ $targetUser: the user whom to send watchlist email notification $title: the page title $enotif: EmailNotification object +'SessionCheckInfo': Validate a MediaWiki\Session\SessionInfo as it's being +loaded from storage. Return false to prevent it from being used. +&$reason: String rejection reason to be logged +$info: MediaWiki\Session\SessionInfo being validated +$request: WebRequest being loaded from +$metadata: Array|false Metadata array for the MediaWiki\Session\Session +$data: Array|false Data array for the MediaWiki\Session\Session + +'SessionMetadata': Add metadata to a session being saved. +$backend: MediaWiki\Session\SessionBackend being saved. +&$metadata: Array Metadata to be stored. Add new keys here. +$requests: Array of WebRequests potentially being saved to. Generally 0-1 real + request and 0+ FauxRequests. + 'SetupAfterCache': Called in Setup.php, after cache objects are set 'ShortPagesQuery': Allow extensions to modify the query used by @@ -3307,8 +3323,9 @@ $name: user name $user: user object &$s: database query object -'UserLoadFromSession': Called to authenticate users on external/environmental -means; occurs before session is loaded. +'UserLoadFromSession': DEPRECATED! Create a MediaWiki\Session\SessionProvider instead. +Called to authenticate users on external/environmental means; occurs before +session is loaded. $user: user object being loaded &$result: set this to a boolean value to abort the normal authentication process @@ -3399,9 +3416,13 @@ $user: User object 'UserSaveSettings': Called when saving user settings. $user: User object -'UserSetCookies': Called when setting user cookies. +'UserSetCookies': DEPRECATED! If you're trying to replace core session cookie +handling, you want to create a subclass of MediaWiki\Session\CookieSessionProvider +instead. Otherwise, you can no longer count on user data being saved to cookies +versus some other mechanism. +Called when setting user cookies. $user: User object -&$session: session array, will be added to $_SESSION +&$session: session array, will be added to the session &$cookies: cookies array mapping cookie name to its value 'UserSetEmail': Called when changing user email address. @@ -3462,7 +3483,7 @@ Return false to prevent setting of the cookie. &$name: Cookie name passed to WebResponse::setcookie() &$value: Cookie value passed to WebResponse::setcookie() &$expire: Cookie expiration, as for PHP's setcookie() -$options: Options passed to WebResponse::setcookie() +&$options: Options passed to WebResponse::setcookie() 'wfShellWikiCmd': Called when generating a shell-escaped command line string to run a MediaWiki cli script. diff --git a/images/.htaccess b/images/.htaccess index 3f3d41e7fa..4e253b678a 100644 --- a/images/.htaccess +++ b/images/.htaccess @@ -1,4 +1,4 @@ -# Protect against bug 28235 +# Protect against bug T30235 RewriteEngine On RewriteOptions inherit diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index f30854aaf1..a6a0c75b54 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -75,7 +75,7 @@ $wgConfigRegistry = array( * MediaWiki version number * @since 1.2 */ -$wgVersion = '1.27alpha'; +$wgVersion = '1.27.0-alpha'; /** * Name of the site. It must be changed in LocalSettings.php @@ -904,7 +904,8 @@ $wgMediaHandlers = array( /** * Plugins for page content model handling. - * Each entry in the array maps a model id to a class name. + * Each entry in the array maps a model id to a class name or callback + * that creates an instance of the appropriate ContentHandler subclass. * * @since 1.21 */ @@ -2146,7 +2147,7 @@ $wgMessageCacheType = CACHE_ANYTHING; $wgParserCacheType = CACHE_ANYTHING; /** - * The cache type for storing session data. Used if $wgSessionsInObjectCache is true. + * The cache type for storing session data. * * For available types see $wgMainCacheType. */ @@ -2281,30 +2282,29 @@ $wgParserCacheExpireTime = 86400; * * @deprecated since 1.20; Use $wgSessionsInObjectCache */ -$wgSessionsInMemcached = false; +$wgSessionsInMemcached = true; /** - * Store sessions in an object cache, configured by $wgSessionCacheType. This - * can be useful to improve performance, or to avoid the locking behavior of - * PHP's default session handler, which tends to prevent multiple requests for - * the same user from acting concurrently. + * @deprecated since 1.27, session data is always stored in object cache. */ -$wgSessionsInObjectCache = false; +$wgSessionsInObjectCache = true; /** - * The expiry time to use for session storage when $wgSessionsInObjectCache is - * enabled, in seconds. + * The expiry time to use for session storage, in seconds. */ $wgObjectCacheSessionExpiry = 3600; /** - * This is used for setting php's session.save_handler. In practice, you will - * almost never need to change this ever. Other options might be 'user' or - * 'session_mysql.' Setting to null skips setting this entirely (which might be - * useful if you're doing cross-application sessions, see bug 11381) + * @deprecated since 1.27, MediaWiki\\Session\\SessionManager doesn't use PHP session storage. */ $wgSessionHandler = null; +/** + * Whether to use PHP session handling ($_SESSION and session_*() functions) + * @var string 'enable', 'warn', or 'disable' + */ +$wgPHPSessionHandling = 'enable'; + /** * If enabled, will send MemCached debugging information to $wgDebugLogFile */ @@ -4633,6 +4633,42 @@ $wgUserrightsInterwikiDelimiter = '@'; */ $wgSecureLogin = false; +/** + * Versioning for authentication tokens. + * + * If non-null, this is combined with the user's secret (the user_token field + * in the DB) to generate the token cookie. Changing this will invalidate all + * active sessions (i.e. it will log everyone out). + * + * @since 1.27 + * @var string|null + */ +$wgAuthenticationTokenVersion = null; + +/** + * MediaWiki\Session\SessionProvider configuration. + * + * Value is an array of ObjectFactory specifications for the SessionProviders + * to be used. Keys in the array are ignored. Order is not significant. + * + * @since 1.27 + */ +$wgSessionProviders = array( + 'MediaWiki\\Session\\CookieSessionProvider' => array( + 'class' => 'MediaWiki\\Session\\CookieSessionProvider', + 'args' => array( array( + 'priority' => 30, + 'callUserSetCookiesHook' => true, + ) ), + ), + 'MediaWiki\\Session\\BotPasswordSessionProvider' => array( + 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', + 'args' => array( array( + 'priority' => 40, + ) ), + ), +); + /** @} */ # end user accounts } /************************************************************************//** @@ -5469,6 +5505,29 @@ $wgGrantPermissionGroups = array( 'highvolume' => 'high-volume', ); +/** + * @var bool Whether to enable bot passwords + * @since 1.27 + */ +$wgEnableBotPasswords = true; + +/** + * Cluster for the bot_passwords table + * @var string|bool If false, the normal cluster will be used + * @since 1.27 + */ +$wgBotPasswordsCluster = false; + +/** + * Database name for the bot_passwords table + * + * To use a database with a table prefix, set this variable to + * "{$database}-{$prefix}". + * @var string|bool If false, the normal database will be used + * @since 1.27 + */ +$wgBotPasswordsDatabase = false; + /** @} */ # end of user rights settings /************************************************************************//** @@ -7906,6 +7965,25 @@ $wgPopularPasswordFile = __DIR__ . '/../serialized/commonpasswords.cdb'; */ $wgMaxUserDBWriteDuration = false; +/** + * Mapping of event channels to EventRelayer configuration. + * + * By setting up a PubSub system (like Kafka) and enabling a corresponding EventRelayer class + * that uses it, MediaWiki can broadcast events to all subscribers. Certain features like WAN + * cache purging and CDN cache purging will emit events to this system. Appropriate listers can + * subscribe to the channel and take actions based on the events. For example, a local daemon + * can run on each CDN cache node and perfom local purges based on the URL purge channel events. + * + * The 'default' channel is for all channels without an explicit entry here. + * + * @since 1.27 + */ +$wgEventRelayerConfig = array( + 'default' => array( + 'class' => 'EventRelayerNull', + ) +); + /** * For really cool vim folding this needs to be at the end: * vim: foldmarker=@{,@} foldmethod=marker diff --git a/includes/DerivativeRequest.php b/includes/DerivativeRequest.php index dda1358f11..4c149ae3ef 100644 --- a/includes/DerivativeRequest.php +++ b/includes/DerivativeRequest.php @@ -61,6 +61,10 @@ class DerivativeRequest extends FauxRequest { return $this->base->getAllHeaders(); } + public function getSession() { + return $this->base->getSession(); + } + public function getSessionData( $key ) { return $this->base->getSessionData( $key ); } diff --git a/includes/EditPage.php b/includes/EditPage.php index 277a6cc6d8..914bad4743 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -2232,8 +2232,6 @@ class EditPage { $wgOut->addModules( 'mediawiki.action.edit.stash' ); } - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - # Enabled article-related sidebar, toplinks, etc. $wgOut->setArticleRelated( true ); diff --git a/includes/EventRelayerGroup.php b/includes/EventRelayerGroup.php new file mode 100644 index 0000000000..3af756dccb --- /dev/null +++ b/includes/EventRelayerGroup.php @@ -0,0 +1,58 @@ +configByChannel = $config->get( 'EventRelayerConfig' ); + } + + /** + * @return EventRelayerGroup + */ + public static function singleton() { + if ( !self::$instance ) { + self::$instance = new self( RequestContext::getMain()->getConfig() ); + } + + return self::$instance; + } + + /** + * @param string $channel + * @return EventRelayer Relayer instance that handles the given channel + */ + public function getRelayer( $channel ) { + $channelKey = isset( $this->configByChannel[$channel] ) + ? $channel + : 'default'; + + if ( !isset( $this->relayers[$channelKey] ) ) { + if ( !isset( $this->configByChannel[$channelKey] ) ) { + throw new UnexpectedValueException( "No config for '$channelKey'" ); + } + + $config = $this->configByChannel[$channelKey]; + $class = $config['class']; + + $this->relayers[$channelKey] = new $class( $config ); + } + + return $this->relayers[$channelKey]; + } +} diff --git a/includes/FauxRequest.php b/includes/FauxRequest.php index 888f853a4d..f049d2ece0 100644 --- a/includes/FauxRequest.php +++ b/includes/FauxRequest.php @@ -23,6 +23,8 @@ * @file */ +use MediaWiki\Session\SessionManager; + /** * WebRequest clone which takes values from a provided array. * @@ -30,7 +32,6 @@ */ class FauxRequest extends WebRequest { private $wasPosted = false; - private $session = array(); private $requestUrl; protected $cookies = array(); @@ -38,7 +39,8 @@ class FauxRequest extends WebRequest { * @param array $data Array of *non*-urlencoded key => value pairs, the * fake GET/POST values * @param bool $wasPosted Whether to treat the data as POST - * @param array|null $session Session array or null + * @param MediaWiki\\Session\\Session|array|null $session Session, session + * data array, or null * @param string $protocol 'http' or 'https' * @throws MWException */ @@ -53,8 +55,16 @@ class FauxRequest extends WebRequest { throw new MWException( "FauxRequest() got bogus data" ); } $this->wasPosted = $wasPosted; - if ( $session ) { - $this->session = $session; + if ( $session instanceof MediaWiki\Session\Session ) { + $this->sessionId = $session->getSessionId(); + } elseif ( is_array( $session ) ) { + $mwsession = SessionManager::singleton()->getEmptySession( $this ); + $this->sessionId = $mwsession->getSessionId(); + foreach ( $session as $key => $value ) { + $mwsession->set( $key, $value ); + } + } elseif ( $session !== null ) { + throw new MWException( "FauxRequest() got bogus session" ); } $this->protocol = $protocol; } @@ -140,10 +150,6 @@ class FauxRequest extends WebRequest { } } - public function checkSessionCookie() { - return false; - } - /** * @since 1.25 */ @@ -186,31 +192,15 @@ class FauxRequest extends WebRequest { } /** - * @param string $key * @return array|null */ - public function getSessionData( $key ) { - if ( isset( $this->session[$key] ) ) { - return $this->session[$key]; + public function getSessionArray() { + if ( $this->sessionId !== null ) { + return iterator_to_array( $this->getSession() ); } return null; } - /** - * @param string $key - * @param array $data - */ - public function setSessionData( $key, $data ) { - $this->session[$key] = $data; - } - - /** - * @return array|mixed|null - */ - public function getSessionArray() { - return $this->session; - } - /** * FauxRequests shouldn't depend on raw request data (but that could be implemented here) * @return string diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 1f9d14ea1d..66201b5ee4 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -26,6 +26,7 @@ if ( !defined( 'MEDIAWIKI' ) ) { use Liuggio\StatsdClient\Sender\SocketSender; use MediaWiki\Logger\LoggerFactory; +use MediaWiki\Session\SessionManager; // Hide compatibility functions from Doxygen /// @cond @@ -3010,9 +3011,12 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, /** * Check if there is sufficient entropy in php's built-in session generation * + * @deprecated since 1.27, PHP's session generation isn't used with + * MediaWiki\\Session\\SessionManager * @return bool True = there is sufficient entropy */ function wfCheckEntropy() { + wfDeprecated( __FUNCTION__, '1.27' ); return ( ( wfIsWindows() && version_compare( PHP_VERSION, '5.3.3', '>=' ) ) || ini_get( 'session.entropy_file' ) @@ -3021,83 +3025,64 @@ function wfCheckEntropy() { } /** - * Override session_id before session startup if php's built-in - * session generation code is not secure. + * @deprecated since 1.27, PHP's session generation isn't used with + * MediaWiki\\Session\\SessionManager */ function wfFixSessionID() { - // If the cookie or session id is already set we already have a session and should abort - if ( isset( $_COOKIE[session_name()] ) || session_id() ) { - return; - } - - // PHP's built-in session entropy is enabled if: - // - entropy_file is set or you're on Windows with php 5.3.3+ - // - AND entropy_length is > 0 - // We treat it as disabled if it doesn't have an entropy length of at least 32 - $entropyEnabled = wfCheckEntropy(); - - // If built-in entropy is not enabled or not sufficient override PHP's - // built in session id generation code - if ( !$entropyEnabled ) { - wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, " . - "overriding session id generation using our cryptrand source.\n" ); - session_id( MWCryptRand::generateHex( 32 ) ); - } + wfDeprecated( __FUNCTION__, '1.27' ); } /** - * Reset the session_id + * Reset the session id * + * @deprecated since 1.27, use MediaWiki\\Session\\SessionManager instead * @since 1.22 */ function wfResetSessionID() { - global $wgCookieSecure; - $oldSessionId = session_id(); - $cookieParams = session_get_cookie_params(); - if ( wfCheckEntropy() && $wgCookieSecure == $cookieParams['secure'] ) { - session_regenerate_id( false ); - } else { - $tmp = $_SESSION; - session_destroy(); - wfSetupSession( MWCryptRand::generateHex( 32 ) ); - $_SESSION = $tmp; + wfDeprecated( __FUNCTION__, '1.27' ); + $session = SessionManager::getGlobalSession(); + $delay = $session->delaySave(); + + $session->resetId(); + + // Make sure a session is started, since that's what the old + // wfResetSessionID() did. + if ( session_id() !== $session->getId() ) { + wfSetupSession( $session->getId() ); } - $newSessionId = session_id(); + + ScopedCallback::consume( $delay ); } /** * Initialise php session * - * @param bool $sessionId + * @deprecated since 1.27, use MediaWiki\\Session\\SessionManager instead. + * Generally, "using" SessionManager will be calling ->getSessionById() or + * ::getGlobalSession() (depending on whether you were passing $sessionId + * here), then calling $session->persist(). + * @param bool|string $sessionId */ function wfSetupSession( $sessionId = false ) { - global $wgSessionsInObjectCache, $wgSessionHandler; - global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly; + wfDeprecated( __FUNCTION__, '1.27' ); - if ( $wgSessionsInObjectCache ) { - ObjectCacheSessionHandler::install(); - } elseif ( $wgSessionHandler && $wgSessionHandler != ini_get( 'session.save_handler' ) ) { - # Only set this if $wgSessionHandler isn't null and session.save_handler - # hasn't already been set to the desired value (that causes errors) - ini_set( 'session.save_handler', $wgSessionHandler ); + // If they're calling this, they probably want our session management even + // if NO_SESSION was set for Setup.php. + if ( !MediaWiki\Session\PHPSessionHandler::isInstalled() ) { + MediaWiki\Session\PHPSessionHandler::install( SessionManager::singleton() ); } - session_set_cookie_params( - 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly ); - session_cache_limiter( 'private, must-revalidate' ); if ( $sessionId ) { session_id( $sessionId ); - } else { - wfFixSessionID(); } - MediaWiki\suppressWarnings(); - session_start(); - MediaWiki\restoreWarnings(); + $session = SessionManager::getGlobalSession(); + $session->persist(); - if ( $wgSessionsInObjectCache ) { - ObjectCacheSessionHandler::renewCurrentSession(); + if ( session_id() !== $session->getId() ) { + session_id( $session->getId() ); } + MediaWiki\quietCall( 'session_start' ); } /** diff --git a/includes/LinkTarget.php b/includes/LinkTarget.php new file mode 100644 index 0000000000..1ce5f32339 --- /dev/null +++ b/includes/LinkTarget.php @@ -0,0 +1,41 @@ + (int): Stub threshold to use when determining link classes. * @return string HTML attribute */ public static function link( @@ -218,7 +219,7 @@ class Linker { $target = self::normaliseSpecialPage( $target ); # If we don't know whether the page exists, let's find out. - if ( !in_array( 'known', $options ) && !in_array( 'broken', $options ) ) { + if ( !in_array( 'known', $options, true ) && !in_array( 'broken', $options, true ) ) { if ( $target->isKnown() ) { $options[] = 'known'; } else { @@ -227,14 +228,14 @@ class Linker { } $oldquery = array(); - if ( in_array( "forcearticlepath", $options ) && $query ) { + if ( in_array( "forcearticlepath", $options, true ) && $query ) { $oldquery = $query; $query = array(); } # Note: we want the href attribute first, for prettiness. $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) ); - if ( in_array( 'forcearticlepath', $options ) && $oldquery ) { + if ( in_array( 'forcearticlepath', $options, true ) && $oldquery ) { $attribs['href'] = wfAppendQuery( $attribs['href'], $oldquery ); } @@ -277,7 +278,7 @@ class Linker { private static function linkUrl( $target, $query, $options ) { # We don't want to include fragments for broken links, because they # generally make no sense. - if ( in_array( 'broken', $options ) && $target->hasFragment() ) { + if ( in_array( 'broken', $options, true ) && $target->hasFragment() ) { $target = clone $target; $target->setFragment( '' ); } @@ -285,15 +286,15 @@ class Linker { # If it's a broken link, add the appropriate query pieces, unless # there's already an action specified, or unless 'edit' makes no sense # (i.e., for a nonexistent special page). - if ( in_array( 'broken', $options ) && empty( $query['action'] ) + if ( in_array( 'broken', $options, true ) && empty( $query['action'] ) && !$target->isSpecialPage() ) { $query['action'] = 'edit'; $query['redlink'] = '1'; } - if ( in_array( 'http', $options ) ) { + if ( in_array( 'http', $options, true ) ) { $proto = PROTO_HTTP; - } elseif ( in_array( 'https', $options ) ) { + } elseif ( in_array( 'https', $options, true ) ) { $proto = PROTO_HTTPS; } else { $proto = PROTO_RELATIVE; @@ -316,11 +317,11 @@ class Linker { global $wgUser; $defaults = array(); - if ( !in_array( 'noclasses', $options ) ) { + if ( !in_array( 'noclasses', $options, true ) ) { # Now build the classes. $classes = array(); - if ( in_array( 'broken', $options ) ) { + if ( in_array( 'broken', $options, true ) ) { $classes[] = 'new'; } @@ -328,8 +329,11 @@ class Linker { $classes[] = 'extiw'; } - if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387) - $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() ); + if ( !in_array( 'broken', $options, true ) ) { # Avoid useless calls to LinkCache (see r50387) + $colour = self::getLinkColour( + $target, + isset( $options['stubThreshold'] ) ? $options['stubThreshold'] : $wgUser->getStubThreshold() + ); if ( $colour !== '' ) { $classes[] = $colour; # mw-redirect or stub } @@ -343,7 +347,7 @@ class Linker { if ( $target->getPrefixedText() == '' ) { # A link like [[#Foo]]. This used to mean an empty title # attribute, but that's silly. Just don't output a title. - } elseif ( in_array( 'known', $options ) ) { + } elseif ( in_array( 'known', $options, true ) ) { $defaults['title'] = $target->getPrefixedText(); } else { // This ends up in parser cache! @@ -1845,7 +1849,7 @@ class Linker { } $editCount = false; - if ( in_array( 'verify', $options ) ) { + if ( in_array( 'verify', $options, true ) ) { $editCount = self::getRollbackEditCount( $rev, true ); if ( $editCount === false ) { return ''; @@ -1854,7 +1858,7 @@ class Linker { $inner = self::buildRollbackLink( $rev, $context, $editCount ); - if ( !in_array( 'noBrackets', $options ) ) { + if ( !in_array( 'noBrackets', $options, true ) ) { $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped(); } diff --git a/includes/MediaWiki.php b/includes/MediaWiki.php index 4939c4d7ff..7f2f737d08 100644 --- a/includes/MediaWiki.php +++ b/includes/MediaWiki.php @@ -673,8 +673,10 @@ class MediaWiki { if ( $request->getProtocol() == 'http' && ( + $request->getSession()->shouldForceHTTPS() || + // Check the cookie manually, for paranoia $request->getCookie( 'forceHTTPS', '' ) || - // check for prefixed version for currently logged in users + // check for prefixed version that was used for a time in older MW versions $request->getCookie( 'forceHTTPS' ) || // Avoid checking the user and groups unless it's enabled. ( diff --git a/includes/MergeHistory.php b/includes/MergeHistory.php new file mode 100644 index 0000000000..a3861eefb6 --- /dev/null +++ b/includes/MergeHistory.php @@ -0,0 +1,351 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * Handles the backend logic of merging the histories of two + * pages. + * + * @since 1.27 + */ +class MergeHistory { + + /** @const int Maximum number of revisions that can be merged at once (avoid too much slave lag) */ + const REVISION_LIMIT = 5000; + + /** @var Title Page from which history will be merged */ + protected $source; + + /** @var Title Page to which history will be merged */ + protected $dest; + + /** @var DatabaseBase Database that we are using */ + protected $dbw; + + /** @var MWTimestamp Maximum timestamp that we can use (oldest timestamp of dest) */ + protected $maxTimestamp; + + /** @var string SQL WHERE condition that selects source revisions to insert into destination */ + protected $timeWhere; + + /** @var MWTimestamp|boolean Timestamp upto which history from the source will be merged */ + protected $timestampLimit; + + /** @var integer Number of revisions merged (for Special:MergeHistory success message) */ + protected $revisionsMerged; + + /** + * MergeHistory constructor. + * @param Title $source Page from which history will be merged + * @param Title $dest Page to which history will be merged + * @param string|boolean $timestamp Timestamp up to which history from the source will be merged + */ + public function __construct( Title $source, Title $dest, $timestamp = false ) { + // Save the parameters + $this->source = $source; + $this->dest = $dest; + + // Get the database + $this->dbw = wfGetDB( DB_MASTER ); + + // Max timestamp should be min of destination page + $firstDestTimestamp = $this->dbw->selectField( + 'revision', + 'MIN(rev_timestamp)', + array( 'rev_page' => $this->dest->getArticleID() ), + __METHOD__ + ); + $this->maxTimestamp = new MWTimestamp( $firstDestTimestamp ); + + // Get the timestamp pivot condition + try { + if ( $timestamp ) { + // If we have a requested timestamp, use the + // latest revision up to that point as the insertion point + $mwTimestamp = new MWTimestamp( $timestamp ); + $lastWorkingTimestamp = $this->dbw->selectField( + 'revision', + 'MAX(rev_timestamp)', + array( + 'rev_timestamp <= ' . $this->dbw->timestamp( $mwTimestamp ), + 'rev_page' => $this->source->getArticleID() + ), + __METHOD__ + ); + $mwLastWorkingTimestamp = new MWTimestamp( $lastWorkingTimestamp ); + + $timeInsert = $mwLastWorkingTimestamp; + $this->timestampLimit = $mwLastWorkingTimestamp; + } else { + // If we don't, merge entire source page history into the + // beginning of destination page history + + // Get the latest timestamp of the source + $lastSourceTimestamp = $this->dbw->selectField( + array( 'page', 'revision' ), + 'rev_timestamp', + array( 'page_id' => $this->source->getArticleID(), + 'page_latest = rev_id' + ), + __METHOD__ + ); + $lasttimestamp = new MWTimestamp( $lastSourceTimestamp ); + + $timeInsert = $this->maxTimestamp; + $this->timestampLimit = $lasttimestamp; + } + + $this->timeWhere = "rev_timestamp <= {$this->dbw->timestamp( $timeInsert )}"; + } catch ( TimestampException $ex ) { + // The timestamp we got is screwed up and merge cannot continue + // This should be detected by $this->isValidMerge() + $this->timestampLimit = false; + } + } + + /** + * Get the number of revisions that will be moved + * @return int + */ + public function getRevisionCount() { + $count = $this->dbw->selectRowCount( 'revision', '1', + array( 'rev_page' => $this->source->getArticleID(), $this->timeWhere ), + __METHOD__, + array( 'LIMIT' => self::REVISION_LIMIT + 1 ) + ); + + return $count; + } + + /** + * Get the number of revisions that were moved + * Used in the SpecialMergeHistory success message + * @return int + */ + public function getMergedRevisionCount() { + return $this->revisionsMerged; + } + + /** + * Check if the merge is possible + * @param User $user + * @param string $reason + * @return Status + */ + public function checkPermissions( User $user, $reason ) { + $status = new Status(); + + // Check if user can edit both pages + $errors = wfMergeErrorArrays( + $this->source->getUserPermissionsErrors( 'edit', $user ), + $this->dest->getUserPermissionsErrors( 'edit', $user ) + ); + + // Convert into a Status object + if ( $errors ) { + foreach ( $errors as $error ) { + call_user_func_array( array( $status, 'fatal' ), $error ); + } + } + + // Anti-spam + if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) { + // This is kind of lame, won't display nice + $status->fatal( 'spamprotectiontext' ); + } + + // Check mergehistory permission + if ( !$user->isAllowed( 'mergehistory' ) ) { + // User doesn't have the right to merge histories + $status->fatal( 'mergehistory-fail-permission' ); + } + + return $status; + } + + /** + * Does various sanity checks that the merge is + * valid. Only things based on the two pages + * should be checked here. + * + * @return Status + */ + public function isValidMerge() { + $status = new Status(); + + // If either article ID is 0, then revisions cannot be reliably selected + if ( $this->source->getArticleID() === 0 ) { + $status->fatal( 'mergehistory-fail-invalid-source' ); + } + if ( $this->dest->getArticleID() === 0 ) { + $status->fatal( 'mergehistory-fail-invalid-dest' ); + } + + // Make sure page aren't the same + if ( $this->source->equals( $this->dest ) ) { + $status->fatal( 'mergehistory-fail-self-merge' ); + } + + // Make sure the timestamp is valid + if ( !$this->timestampLimit ) { + $status->fatal( 'mergehistory-fail-bad-timestamp' ); + } + + // $this->timestampLimit must be older than $this->maxTimestamp + if ( $this->timestampLimit > $this->maxTimestamp ) { + $status->fatal( 'mergehistory-fail-timestamps-overlap' ); + } + + // Check that there are not too many revisions to move + if ( $this->timestampLimit && $this->getRevisionCount() > self::REVISION_LIMIT ) { + $status->fatal( 'mergehistory-fail-toobig', Message::numParam( self::REVISION_LIMIT ) ); + } + + return $status; + } + + /** + * Actually attempt the history move + * + * @todo if all versions of page A are moved to B and then a user + * tries to do a reverse-merge via the "unmerge" log link, then page + * A will still be a redirect (as it was after the original merge), + * though it will have the old revisions back from before (as expected). + * The user may have to "undo" the redirect manually to finish the "unmerge". + * Maybe this should delete redirects at the source page of merges? + * + * @param User $user + * @param string $reason + * @return Status status of the history merge + */ + public function merge( User $user, $reason = '' ) { + $status = new Status(); + + // Check validity and permissions required for merge + $validCheck = $this->isValidMerge(); // Check this first to check for null pages + if ( !$validCheck->isOK() ) { + return $validCheck; + } + $permCheck = $this->checkPermissions( $user, $reason ); + if ( !$permCheck->isOK() ) { + return $permCheck; + } + + $this->dbw->update( + 'revision', + array( 'rev_page' => $this->dest->getArticleID() ), + array( 'rev_page' => $this->source->getArticleID(), $this->timeWhere ), + __METHOD__ + ); + + // Check if this did anything + $this->revisionsMerged = $this->dbw->affectedRows(); + if ( $this->revisionsMerged < 1 ) { + $status->fatal( 'mergehistory-fail-no-change' ); + return $status; + } + + // Make the source page a redirect if no revisions are left + $haveRevisions = $this->dbw->selectField( + 'revision', + 'rev_timestamp', + array( 'rev_page' => $this->source->getArticleID() ), + __METHOD__, + array( 'FOR UPDATE' ) + ); + if ( !$haveRevisions ) { + if ( $reason ) { + $reason = wfMessage( + 'mergehistory-comment', + $this->source->getPrefixedText(), + $this->dest->getPrefixedText(), + $reason + )->inContentLanguage()->text(); + } else { + $reason = wfMessage( + 'mergehistory-autocomment', + $this->source->getPrefixedText(), + $this->dest->getPrefixedText() + )->inContentLanguage()->text(); + } + + $contentHandler = ContentHandler::getForTitle( $this->source ); + $redirectContent = $contentHandler->makeRedirectContent( + $this->dest, + wfMessage( 'mergehistory-redirect-text' )->inContentLanguage()->plain() + ); + + if ( $redirectContent ) { + $redirectPage = WikiPage::factory( $this->source ); + $redirectRevision = new Revision( array( + 'title' => $this->source, + 'page' => $this->source->getArticleID(), + 'comment' => $reason, + 'content' => $redirectContent ) ); + $redirectRevision->insertOn( $this->dbw ); + $redirectPage->updateRevisionOn( $this->dbw, $redirectRevision ); + + // Now, we record the link from the redirect to the new title. + // It should have no other outgoing links... + $this->dbw->delete( + 'pagelinks', + array( 'pl_from' => $this->dest->getArticleID() ), + __METHOD__ + ); + $this->dbw->insert( 'pagelinks', + array( + 'pl_from' => $this->dest->getArticleID(), + 'pl_from_namespace' => $this->dest->getNamespace(), + 'pl_namespace' => $this->dest->getNamespace(), + 'pl_title' => $this->dest->getDBkey() ), + __METHOD__ + ); + } else { + // Warning if we couldn't create the redirect + $status->warning( 'mergehistory-warning-redirect-not-created' ); + } + } else { + $this->source->invalidateCache(); // update histories + } + $this->dest->invalidateCache(); // update histories + + // Update our logs + $logEntry = new ManualLogEntry( 'merge', 'merge' ); + $logEntry->setPerformer( $user ); + $logEntry->setComment( $reason ); + $logEntry->setTarget( $this->source ); + $logEntry->setParameters( array( + '4::dest' => $this->dest->getPrefixedText(), + '5::mergepoint' => $this->timestampLimit->getTimestamp( TS_MW ) + ) ); + $logId = $logEntry->insert(); + $logEntry->publish( $logId ); + + Hooks::run( 'ArticleMergeComplete', array( $this->source, $this->dest ) ); + + return $status; + } +} diff --git a/includes/Message.php b/includes/Message.php index 54efd261b6..c71a953498 100644 --- a/includes/Message.php +++ b/includes/Message.php @@ -271,7 +271,7 @@ class Message implements MessageSpecifier, Serializable { public function serialize() { return serialize( array( 'interface' => $this->interface, - 'language' => $this->language->getCode(), + 'language' => $this->language instanceof StubUserLang ? false : $this->language->getCode(), 'key' => $this->key, 'keysToTry' => $this->keysToTry, 'parameters' => $this->parameters, @@ -287,6 +287,8 @@ class Message implements MessageSpecifier, Serializable { * @param string $serialized */ public function unserialize( $serialized ) { + global $wgLang; + $data = unserialize( $serialized ); $this->interface = $data['interface']; $this->key = $data['key']; @@ -294,7 +296,7 @@ class Message implements MessageSpecifier, Serializable { $this->parameters = $data['parameters']; $this->format = $data['format']; $this->useDatabase = $data['useDatabase']; - $this->language = Language::factory( $data['language'] ); + $this->language = $data['language'] ? Language::factory( $data['language'] ) : $wgLang; $this->title = $data['title']; } diff --git a/includes/OutputPage.php b/includes/OutputPage.php index 97165b4613..e527001b5a 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -21,6 +21,7 @@ */ use MediaWiki\Logger\LoggerFactory; +use MediaWiki\Session\SessionManager; use WrappedString\WrappedString; /** @@ -1572,11 +1573,42 @@ class OutputPage extends ContextSource { * @return ParserOptions */ public function parserOptions( $options = null ) { + if ( $options !== null && !empty( $options->isBogus ) ) { + // Someone is trying to set a bogus pre-$wgUser PO. Check if it has + // been changed somehow, and keep it if so. + $anonPO = ParserOptions::newFromAnon(); + $anonPO->setEditSection( false ); + if ( !$options->matches( $anonPO ) ) { + wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) ); + $options->isBogus = false; + } + } + if ( !$this->mParserOptions ) { + if ( !$this->getContext()->getUser()->isSafeToLoad() ) { + // $wgUser isn't unstubbable yet, so don't try to get a + // ParserOptions for it. And don't cache this ParserOptions + // either. + $po = ParserOptions::newFromAnon(); + $po->setEditSection( false ); + $po->isBogus = true; + if ( $options !== null ) { + $this->mParserOptions = empty( $options->isBogus ) ? $options : null; + } + return $po; + } + $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); $this->mParserOptions->setEditSection( false ); } - return wfSetVar( $this->mParserOptions, $options ); + + if ( $options !== null && !empty( $options->isBogus ) ) { + // They're trying to restore the bogus pre-$wgUser PO. Do the right + // thing. + return wfSetVar( $this->mParserOptions, null, true ); + } else { + return wfSetVar( $this->mParserOptions, $options ); + } } /** @@ -1977,11 +2009,9 @@ class OutputPage extends ContextSource { if ( $cookies === null ) { $config = $this->getConfig(); $cookies = array_merge( + SessionManager::singleton()->getVaryCookies(), array( - $config->get( 'CookiePrefix' ) . 'Token', - $config->get( 'CookiePrefix' ) . 'LoggedOut', - "forceHTTPS", - session_name() + 'forceHTTPS', ), $config->get( 'CacheVaryCookies' ) ); @@ -2033,6 +2063,9 @@ class OutputPage extends ContextSource { * @return string */ public function getVaryHeader() { + foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) { + $this->addVaryHeader( $header, $options ); + } return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ); } @@ -2050,6 +2083,10 @@ class OutputPage extends ContextSource { } $this->addVaryHeader( 'Cookie', $cookiesOption ); + foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) { + $this->addVaryHeader( $header, $options ); + } + $headers = array(); foreach ( $this->mVaryHeader as $header => $option ) { $newheader = $header; @@ -2173,8 +2210,8 @@ class OutputPage extends ContextSource { if ( $this->mEnableClientCache ) { if ( - $config->get( 'UseSquid' ) && session_id() == '' && !$this->isPrintable() && - $this->mCdnMaxage != 0 && !$this->haveCacheVaryCookies() + $config->get( 'UseSquid' ) && !SessionManager::getGlobalSession()->isPersistent() && + !$this->isPrintable() && $this->mCdnMaxage != 0 && !$this->haveCacheVaryCookies() ) { if ( $config->get( 'UseESI' ) ) { # We'll purge the proxy cache explicitly, but require end user agents @@ -3607,8 +3644,6 @@ class OutputPage extends ContextSource { */ public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { $options = array(); - // Even though we expect the media type to be lowercase, but here we - // force it to lowercase to be safe. if ( $media ) { $options['media'] = $media; } @@ -3809,6 +3844,58 @@ class OutputPage extends ContextSource { return $link; } + /** + * Transform path to web-accessible static resource. + * + * This is used to add a validation hash as query string. + * This aids various behaviors: + * + * - Put long Cache-Control max-age headers on responses for improved + * cache performance. + * - Get the correct version of a file as expected by the current page. + * - Instantly get the updated version of a file after deployment. + * + * Avoid using this for urls included in HTML as otherwise clients may get different + * versions of a resource when navigating the site depending on when the page was cached. + * If changes to the url propagate, this is not a problem (e.g. if the url is in + * an external stylesheet). + * + * @since 1.27 + * @param Config $config + * @param string $path Path-absolute URL to file (from document root, must start with "/") + * @return string URL + */ + public static function transformResourcePath( Config $config, $path ) { + global $IP; + $remotePath = $config->get( 'ResourceBasePath' ); + if ( strpos( $path, $remotePath ) !== 0 ) { + // Path is outside wgResourceBasePath, ignore. + return $path; + } + $path = RelPath\getRelativePath( $path, $remotePath ); + return self::transformFilePath( $remotePath, $IP, $path ); + } + + /** + * Utility method for transformResourceFilePath(). + * + * Caller is responsible for ensuring the file exists. Emits a PHP warning otherwise. + * + * @since 1.27 + * @param string $remotePath URL path that points to $localPath + * @param string $localPath File directory exposed at $remotePath + * @param string $file Path to target file relative to $localPath + * @return string URL + */ + public static function transformFilePath( $remotePath, $localPath, $file ) { + $hash = md5_file( "$localPath/$file" ); + if ( $hash === false ) { + wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" ); + $hash = ''; + } + return "$remotePath/$file?" . substr( $hash, 0, 5 ); + } + /** * Transform "media" attribute based on request parameters * @@ -3993,11 +4080,14 @@ class OutputPage extends ContextSource { $this->getLanguage()->getDir() ); $this->addModuleStyles( array( - 'oojs-ui.styles', + 'oojs-ui-core.styles', 'oojs-ui.styles.icons', 'oojs-ui.styles.indicators', 'oojs-ui.styles.textures', 'mediawiki.widgets.styles', ) ); + // Used by 'skipFunction' of the four 'oojs-ui.styles.*' modules. Please don't treat this as a + // public API or you'll be severely disappointed when T87871 is fixed and it disappears. + $this->addMeta( 'X-OOUI-PHP', '1' ); } } diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php index eaab9c8365..1eafcfa5b8 100644 --- a/includes/PHPVersionCheck.php +++ b/includes/PHPVersionCheck.php @@ -31,7 +31,7 @@ */ function wfEntryPointCheck( $entryPoint ) { $mwVersion = '1.27'; - $minimumVersionPHP = '5.3.3'; + $minimumVersionPHP = '5.5.9'; $phpVersion = PHP_VERSION; if ( !function_exists( 'version_compare' ) diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index c6f187d2b7..5f36cf507d 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -23,6 +23,7 @@ /** * Handles searching prefixes of titles and finding any page * names that match. Used largely by the OpenSearch implementation. + * @deprecated Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch * * @ingroup Search */ @@ -259,14 +260,17 @@ abstract class PrefixSearch { * @param int $offset Number of items to skip * @return array Array of Title objects */ - protected function defaultSearchBackend( $namespaces, $search, $limit, $offset ) { + public function defaultSearchBackend( $namespaces, $search, $limit, $offset ) { $ns = array_shift( $namespaces ); // support only one namespace - if ( in_array( NS_MAIN, $namespaces ) ) { + if ( is_null( $ns ) || in_array( NS_MAIN, $namespaces ) ) { $ns = NS_MAIN; // if searching on many always default to main } - $t = Title::newFromText( $search, $ns ); + if ( $ns == NS_SPECIAL ) { + return $this->specialSearch( $search, $limit, $offset ); + } + $t = Title::newFromText( $search, $ns ); $prefix = $t ? $t->getDBkey() : ''; $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'page', @@ -318,6 +322,7 @@ abstract class PrefixSearch { /** * Performs prefix search, returning Title objects + * @deprecated Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch * @ingroup Search */ class TitlePrefixSearch extends PrefixSearch { @@ -337,6 +342,7 @@ class TitlePrefixSearch extends PrefixSearch { /** * Performs prefix search, returning strings + * @deprecated Since 1.27, Use SearchEngine::prefixSearchSubpages or SearchEngine::completionSearch * @ingroup Search */ class StringPrefixSearch extends PrefixSearch { diff --git a/includes/Revision.php b/includes/Revision.php index f4f6dcad46..e76d19ef78 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -101,22 +101,22 @@ class Revision implements IDBAccessObject { /** * Load either the current, or a specified, revision - * that's attached to a given title. If not attached - * to that title, will return null. + * that's attached to a given link target. If not attached + * to that link target, will return null. * * $flags include: * Revision::READ_LATEST : Select the data from the master * Revision::READ_LOCKING : Select & lock the data from the master * - * @param Title $title + * @param LinkTarget $linkTarget * @param int $id (optional) * @param int $flags Bitfield (optional) * @return Revision|null */ - public static function newFromTitle( $title, $id = 0, $flags = 0 ) { + public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) { $conds = array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBkey() + 'page_namespace' => $linkTarget->getNamespace(), + 'page_title' => $linkTarget->getDBkey() ); if ( $id ) { // Use the specified ID diff --git a/includes/Setup.php b/includes/Setup.php index 06962c1edd..b9a1c377bb 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -496,10 +496,26 @@ if ( $wgMaximalPasswordLength !== false ) { $wgPasswordPolicy['policies']['default']['MaximalPasswordLength'] = $wgMaximalPasswordLength; } -// Backwards compatibility with deprecated alias -// Must be before call to wfSetupSession() -if ( $wgSessionsInMemcached ) { - $wgSessionsInObjectCache = true; +// Backwards compatibility warning +if ( !$wgSessionsInObjectCache && !$wgSessionsInMemcached ) { + wfDeprecated( '$wgSessionsInObjectCache = false', '1.27' ); + if ( $wgSessionHandler ) { + wfDeprecated( '$wgSessionsHandler', '1.27' ); + } + $cacheType = get_class( ObjectCache::getInstance( $wgSessionCacheType ) ); + wfDebugLog( + 'caches', + "Session data will be stored in \"$cacheType\" cache with " . + "expiry $wgObjectCacheSessionExpiry seconds" + ); +} +$wgSessionsInObjectCache = true; + +if ( $wgPHPSessionHandling !== 'enable' && + $wgPHPSessionHandling !== 'warn' && + $wgPHPSessionHandling !== 'disable' +) { + $wgPHPSessionHandling = 'warn'; } Profiler::instance()->scopedProfileOut( $ps_default ); @@ -656,20 +672,6 @@ Profiler::instance()->scopedProfileOut( $ps_memcached ); // Most of the config is out, some might want to run hooks here. Hooks::run( 'SetupAfterCache' ); -$ps_session = Profiler::instance()->scopedProfileIn( $fname . '-session' ); - -if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) { - // If session.auto_start is there, we can't touch session name - if ( !wfIniGetBool( 'session.auto_start' ) ) { - session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' ); - } - - if ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix . 'Token'] ) ) { - wfSetupSession(); - } -} - -Profiler::instance()->scopedProfileOut( $ps_session ); $ps_globals = Profiler::instance()->scopedProfileIn( $fname . '-globals' ); /** @@ -682,6 +684,65 @@ $wgContLang->initContLang(); // Now that variant lists may be available... $wgRequest->interpolateTitle(); +if ( !is_object( $wgAuth ) ) { + $wgAuth = new AuthPlugin; + Hooks::run( 'AuthPluginSetup', array( &$wgAuth ) ); +} + +// Set up the session +$ps_session = Profiler::instance()->scopedProfileIn( $fname . '-session' ); +/** + * @var MediaWiki\\Session\\SessionId|null $wgInitialSessionId The persistent + * session ID (if any) loaded at startup + */ +$wgInitialSessionId = null; +if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) { + // If session.auto_start is there, we can't touch session name + if ( $wgPHPSessionHandling !== 'disable' && !wfIniGetBool( 'session.auto_start' ) ) { + session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' ); + } + + // Create the SessionManager singleton and set up our session handler + MediaWiki\Session\PHPSessionHandler::install( + MediaWiki\Session\SessionManager::singleton() + ); + + // Initialize the session + try { + $session = MediaWiki\Session\SessionManager::getGlobalSession(); + } catch ( OverflowException $ex ) { + if ( isset( $ex->sessionInfos ) && count( $ex->sessionInfos ) >= 2 ) { + // The exception is because the request had multiple possible + // sessions tied for top priority. Report this to the user. + $list = array(); + foreach ( $ex->sessionInfos as $info ) { + $list[] = $info->getProvider()->describe( $wgContLang ); + } + $list = $wgContLang->listToText( $list ); + throw new HttpError( 400, + Message::newFromKey( 'sessionmanager-tie', $list )->inLanguage( $wgContLang )->plain() + ); + } + + // Not the one we want, rethrow + throw $ex; + } + + if ( $session->isPersistent() ) { + $wgInitialSessionId = $session->getSessionId(); + } + + $session->renew(); + if ( MediaWiki\Session\PHPSessionHandler::isEnabled() && + ( $session->isPersistent() || $session->shouldRememberUser() ) + ) { + // Start the PHP-session for backwards compatibility + session_id( $session->getId() ); + MediaWiki\quietCall( 'session_start' ); + } +} +Profiler::instance()->scopedProfileOut( $ps_session ); + /** * @var User $wgUser */ @@ -702,11 +763,6 @@ $wgOut = RequestContext::getMain()->getOutput(); // BackCompat */ $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) ); -if ( !is_object( $wgAuth ) ) { - $wgAuth = new AuthPlugin; - Hooks::run( 'AuthPluginSetup', array( &$wgAuth ) ); -} - /** * @var Title $wgTitle */ @@ -738,6 +794,18 @@ foreach ( $wgExtensionFunctions as $func ) { Profiler::instance()->scopedProfileOut( $ps_ext_func ); } +// If the session user has a 0 id but a valid name, that means we need to +// autocreate it. +if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) { + $sessionUser = MediaWiki\Session\SessionManager::getGlobalSession()->getUser(); + if ( $sessionUser->getId() === 0 && User::isValidUserName( $sessionUser->getName() ) ) { + $ps_autocreate = Profiler::instance()->scopedProfileIn( $fname . '-autocreate' ); + MediaWiki\Session\SessionManager::autoCreateUser( $sessionUser ); + Profiler::instance()->scopedProfileOut( $ps_autocreate ); + } + unset( $sessionUser ); +} + wfDebug( "Fully initialised\n" ); $wgFullyInitialised = true; diff --git a/includes/Title.php b/includes/Title.php index 882b7dda78..50721afd42 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -30,7 +30,7 @@ * @note Consider using a TitleValue object instead. TitleValue is more lightweight * and does not rely on global state or the database. */ -class Title { +class Title implements LinkTarget { /** @var HashBagOStuff */ static private $titleCache = null; @@ -161,9 +161,9 @@ class Title { * Avoid usage of this singleton by using TitleValue * and the associated services when possible. * - * @return TitleParser + * @return MediaWikiTitleCodec */ - private static function getTitleParser() { + private static function getMediaWikiTitleCodec() { global $wgContLang, $wgLocalInterwikis; static $titleCodec = null; @@ -200,9 +200,9 @@ class Title { * @return TitleFormatter */ private static function getTitleFormatter() { - // NOTE: we know that getTitleParser() returns a MediaWikiTitleCodec, + // NOTE: we know that getMediaWikiTitleCodec() returns a MediaWikiTitleCodec, // which implements TitleFormatter. - return self::getTitleParser(); + return self::getMediaWikiTitleCodec(); } function __construct() { @@ -236,10 +236,21 @@ class Title { * @return Title */ public static function newFromTitleValue( TitleValue $titleValue ) { + return self::newFromLinkTarget( $titleValue ); + } + + /** + * Create a new Title from a LinkTarget + * + * @param LinkTarget $linkTarget Assumed to be safe. + * + * @return Title + */ + public static function newFromLinkTarget( LinkTarget $linkTarget ) { return self::makeTitle( - $titleValue->getNamespace(), - $titleValue->getText(), - $titleValue->getFragment() ); + $linkTarget->getNamespace(), + $linkTarget->getText(), + $linkTarget->getFragment() ); } /** @@ -3342,7 +3353,7 @@ class Title { // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share // the parsing code with Title, while avoiding massive refactoring. // @todo: get rid of secureAndSplit, refactor parsing code. - $titleParser = self::getTitleParser(); + $titleParser = self::getMediaWikiTitleCodec(); // MalformedTitleException can be thrown here $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() ); diff --git a/includes/WebRequest.php b/includes/WebRequest.php index 7b76592348..4c4ca976de 100644 --- a/includes/WebRequest.php +++ b/includes/WebRequest.php @@ -23,6 +23,8 @@ * @file */ +use MediaWiki\Session\SessionManager; + /** * The WebRequest class encapsulates getting at data passed in the * URL or via a POSTed form stripping illegal input characters and @@ -63,6 +65,13 @@ class WebRequest { */ protected $protocol; + /** + * @var \\MediaWiki\\Session\\SessionId|null Session ID to use for this + * request. We can't save the session directly due to reference cycles not + * working too well (slow GC in Zend and never collected in HHVM). + */ + protected $sessionId = null; + public function __construct() { $this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true ); @@ -638,18 +647,49 @@ class WebRequest { } /** - * Returns true if there is a session cookie set. + * Return the session for this request + * @since 1.27 + * @note For performance, keep the session locally if you will be making + * much use of it instead of calling this method repeatedly. + * @return MediaWiki\\Session\\Session + */ + public function getSession() { + if ( $this->sessionId !== null ) { + $session = SessionManager::singleton()->getSessionById( (string)$this->sessionId, true, $this ); + if ( $session ) { + return $session; + } + } + + $session = SessionManager::singleton()->getSessionForRequest( $this ); + $this->sessionId = $session->getSessionId(); + return $session; + } + + /** + * Set the session for this request + * @since 1.27 + * @private For use by MediaWiki\\Session classes only + * @param MediaWiki\\Session\\SessionId $sessionId + */ + public function setSessionId( MediaWiki\Session\SessionId $sessionId ) { + $this->sessionId = $sessionId; + } + + /** + * Returns true if the request has a persistent session. * This does not necessarily mean that the user is logged in! * - * If you want to check for an open session, use session_id() - * instead; that will also tell you if the session was opened - * during the current request (in which case the cookie will - * be sent back to the client at the end of the script run). - * + * @deprecated since 1.27, use + * \\MediaWiki\\Session\\SessionManager::singleton()->getPersistedSessionId() + * instead. * @return bool */ public function checkSessionCookie() { - return isset( $_COOKIE[session_name()] ); + global $wgInitialSessionId; + wfDeprecated( __METHOD__, '1.27' ); + return $wgInitialSessionId !== null && + $this->getSession()->getId() === (string)$wgInitialSessionId; } /** @@ -907,26 +947,25 @@ class WebRequest { } /** - * Get data from $_SESSION + * Get data from the session * - * @param string $key Name of key in $_SESSION + * @note Prefer $this->getSession() instead if making multiple calls. + * @param string $key Name of key in the session * @return mixed */ public function getSessionData( $key ) { - if ( !isset( $_SESSION[$key] ) ) { - return null; - } - return $_SESSION[$key]; + return $this->getSession()->get( $key ); } /** * Set session data * - * @param string $key Name of key in $_SESSION + * @note Prefer $this->getSession() instead if making multiple calls. + * @param string $key Name of key in the session * @param mixed $data */ public function setSessionData( $key, $data ) { - $_SESSION[$key] = $data; + return $this->getSession()->set( $key, $data ); } /** diff --git a/includes/WebResponse.php b/includes/WebResponse.php index fd48005398..e46d86f826 100644 --- a/includes/WebResponse.php +++ b/includes/WebResponse.php @@ -128,7 +128,7 @@ class WebResponse { $func = $options['raw'] ? 'setrawcookie' : 'setcookie'; - if ( Hooks::run( 'WebResponseSetCookie', array( &$name, &$value, &$expire, $options ) ) ) { + if ( Hooks::run( 'WebResponseSetCookie', array( &$name, &$value, &$expire, &$options ) ) ) { $cookie = $options['prefix'] . $name; $data = array( 'name' => (string)$cookie, diff --git a/includes/actions/EditAction.php b/includes/actions/EditAction.php index 643d1c4d1c..9b70994741 100644 --- a/includes/actions/EditAction.php +++ b/includes/actions/EditAction.php @@ -43,8 +43,9 @@ class EditAction extends FormlessAction { public function show() { $this->useTransactionalTimeLimit(); + $out = $this->getOutput(); + $out->setRobotPolicy( 'noindex,nofollow' ); if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { - $out = $this->getOutput(); $out->addModuleStyles( array( 'mediawiki.ui.input', 'mediawiki.ui.checkbox', diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php index 69cd7aa1bd..b371848e7b 100644 --- a/includes/actions/RawAction.php +++ b/includes/actions/RawAction.php @@ -83,7 +83,8 @@ class RawAction extends FormlessAction { $response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' ); // Output may contain user-specific data; // vary generated content for open sessions on private wikis - $privateCache = !User::isEveryoneAllowed( 'read' ) && ( $smaxage == 0 || session_id() != '' ); + $privateCache = !User::isEveryoneAllowed( 'read' ) && + ( $smaxage == 0 || MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent() ); // Don't accidentally cache cookies if user is logged in (T55032) $privateCache = $privateCache || $this->getUser()->isLoggedIn(); $mode = $privateCache ? 'private' : 'public'; diff --git a/includes/actions/SubmitAction.php b/includes/actions/SubmitAction.php index fae49f61e3..8990b75f75 100644 --- a/includes/actions/SubmitAction.php +++ b/includes/actions/SubmitAction.php @@ -32,10 +32,8 @@ class SubmitAction extends EditAction { } public function show() { - if ( session_id() === '' ) { - // Send a cookie so anons get talk message notifications - wfSetupSession(); - } + // Send a cookie so anons get talk message notifications + MediaWiki\Session\SessionManager::getGlobalSession()->persist(); parent::show(); } diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index b9163d29d5..a6da823118 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -74,6 +74,8 @@ abstract class ApiBase extends ContextSource { * - string: Any non-empty string, not expected to be very long or contain newlines. * would be an appropriate HTML form field. * - submodule: The name of a submodule of this module, see PARAM_SUBMODULE_MAP. + * - tags: A string naming an existing, explicitly-defined tag. Should usually be + * used with PARAM_ISMULTI. * - text: Any non-empty string, expected to be very long or contain newlines. *