2004-02-18 02:15:00 +00:00
|
|
|
<?php
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Implements the User class for the %MediaWiki software.
|
2011-06-28 18:21:59 +00:00
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @file
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
|
|
|
|
|
2020-01-10 00:00:51 +00:00
|
|
|
use MediaWiki\Auth\AuthenticationRequest;
|
|
|
|
|
use MediaWiki\Auth\AuthManager;
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
use MediaWiki\Block\AbstractBlock;
|
2021-03-10 19:40:33 +00:00
|
|
|
use MediaWiki\Block\Block;
|
2019-05-13 14:18:07 +00:00
|
|
|
use MediaWiki\Block\DatabaseBlock;
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
use MediaWiki\Block\SystemBlock;
|
2021-02-06 00:54:54 +00:00
|
|
|
use MediaWiki\DAO\WikiAwareEntityTrait;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
|
2020-01-10 00:00:51 +00:00
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2021-03-26 01:50:30 +00:00
|
|
|
use MediaWiki\Mail\UserEmailContact;
|
2016-04-03 08:37:11 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2021-01-07 20:57:19 +00:00
|
|
|
use MediaWiki\Page\PageIdentity;
|
|
|
|
|
use MediaWiki\Permissions\Authority;
|
|
|
|
|
use MediaWiki\Permissions\PermissionStatus;
|
|
|
|
|
use MediaWiki\Permissions\UserAuthority;
|
2016-02-01 20:44:03 +00:00
|
|
|
use MediaWiki\Session\SessionManager;
|
2020-09-29 17:04:22 +00:00
|
|
|
use MediaWiki\User\UserFactory;
|
2017-11-16 19:44:44 +00:00
|
|
|
use MediaWiki\User\UserIdentity;
|
2021-04-07 17:32:17 +00:00
|
|
|
use MediaWiki\User\UserIdentityValue;
|
Add a new UserNameUtils service
This replaces User::isValidUserName, ::isUsableName, ::isCreatableName,
::getCanonicalName, and ::isIP.
Unlike User::isIP, UserNameUtils::isIP will //not// return true
for IPv6 ranges.
UserNameUtils::isIPRange, like User::isIPRange, accepts a name and
simply calls IPUtils::isValidRange.
User::isValidUserName, ::isUsableName, ::isCreatableName,
::getCanonical, ::isIP, and ::isValidRange are all soft deprecated
A follow up patch will add this to the release notes, to avoid merge
conflicts.
Bug: T245231
Bug: T239527
Change-Id: I46684bc492bb74b728ff102971f6cdd4d746a50a
2020-02-23 23:52:44 +00:00
|
|
|
use MediaWiki\User\UserNameUtils;
|
2021-01-22 19:51:43 +00:00
|
|
|
use Wikimedia\Assert\Assert;
|
2021-01-27 04:25:24 +00:00
|
|
|
use Wikimedia\Assert\PreconditionException;
|
2019-06-25 18:53:15 +00:00
|
|
|
use Wikimedia\IPUtils;
|
2017-02-07 04:49:57 +00:00
|
|
|
use Wikimedia\Rdbms\Database;
|
2017-02-24 16:17:16 +00:00
|
|
|
use Wikimedia\Rdbms\DBExpectedError;
|
2017-09-12 17:12:29 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2020-01-10 00:00:51 +00:00
|
|
|
use Wikimedia\ScopedCallback;
|
2021-10-25 19:56:47 +00:00
|
|
|
use Wikimedia\Timestamp\ConvertibleTimestamp;
|
2016-02-01 20:44:03 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2007-04-24 06:53:31 +00:00
|
|
|
* The User object encapsulates all of the user-specific settings (user_id,
|
2015-09-04 16:17:42 +00:00
|
|
|
* name, rights, email address, options, last login time). Client
|
2007-04-24 06:53:31 +00:00
|
|
|
* classes use the getXXX() functions to access these fields. These functions
|
|
|
|
|
* do all the work of determining whether the user is logged in,
|
|
|
|
|
* whether the requested option can be satisfied from cookies or
|
|
|
|
|
* whether a database query is needed. Most of the settings needed
|
|
|
|
|
* for rendering normal pages are set in the cookie to minimize use
|
|
|
|
|
* of the database.
|
2020-06-30 10:13:50 +00:00
|
|
|
*
|
2021-01-07 20:57:19 +00:00
|
|
|
* @note User implements Authority to ease transition. Always prefer
|
|
|
|
|
* using existing Authority or obtaining a proper Authority implementation.
|
|
|
|
|
*
|
2020-07-16 11:46:28 +00:00
|
|
|
* @newable in 1.35 only, the constructor is @internal since 1.36
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2021-03-10 19:40:33 +00:00
|
|
|
class User implements Authority, UserIdentity, UserEmailContact {
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
use ProtectedHookAccessorTrait;
|
2021-02-06 00:54:54 +00:00
|
|
|
use WikiAwareEntityTrait;
|
2019-02-26 13:02:50 +00:00
|
|
|
|
2021-03-10 19:40:33 +00:00
|
|
|
/**
|
|
|
|
|
* @var int
|
|
|
|
|
* @see IDBAccessObject::READ_EXCLUSIVE
|
|
|
|
|
*/
|
|
|
|
|
public const READ_EXCLUSIVE = IDBAccessObject::READ_EXCLUSIVE;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var int
|
|
|
|
|
* @see IDBAccessObject::READ_LOCKING
|
|
|
|
|
*/
|
|
|
|
|
public const READ_LOCKING = IDBAccessObject::READ_LOCKING;
|
|
|
|
|
|
2011-02-16 19:51:25 +00:00
|
|
|
/**
|
2019-02-26 13:02:50 +00:00
|
|
|
* Number of characters required for the user_token field.
|
2014-07-24 00:26:27 +00:00
|
|
|
*/
|
2020-05-15 22:07:42 +00:00
|
|
|
public const TOKEN_LENGTH = 32;
|
2014-07-24 00:26:27 +00:00
|
|
|
|
2016-02-01 21:59:27 +00:00
|
|
|
/**
|
2019-02-26 13:02:50 +00:00
|
|
|
* An invalid string value for the user_token field.
|
2016-02-01 21:59:27 +00:00
|
|
|
*/
|
2020-05-15 22:07:42 +00:00
|
|
|
public const INVALID_TOKEN = '*** INVALID ***';
|
2016-02-01 21:59:27 +00:00
|
|
|
|
2014-07-24 00:26:27 +00:00
|
|
|
/**
|
2019-02-26 13:02:50 +00:00
|
|
|
* Version number to tag cached versions of serialized User objects. Should be increased when
|
|
|
|
|
* {@link $mCacheVars} or one of it's members changes.
|
2014-07-24 00:26:27 +00:00
|
|
|
*/
|
2021-06-15 08:08:33 +00:00
|
|
|
private const VERSION = 17;
|
2014-07-24 00:26:27 +00:00
|
|
|
|
2016-02-01 11:53:01 +00:00
|
|
|
/**
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
2020-05-15 22:07:42 +00:00
|
|
|
public const CHECK_USER_RIGHTS = true;
|
2016-02-01 11:53:01 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
2020-05-15 22:07:42 +00:00
|
|
|
public const IGNORE_USER_RIGHTS = false;
|
2016-02-01 11:53:01 +00:00
|
|
|
|
2021-05-03 21:34:26 +00:00
|
|
|
/**
|
|
|
|
|
* Username used for various maintenance scripts.
|
|
|
|
|
* @since 1.37
|
|
|
|
|
*/
|
|
|
|
|
public const MAINTENANCE_SCRIPT_USER = 'Maintenance script';
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2020-10-28 10:01:33 +00:00
|
|
|
* List of member variables which are saved to the
|
2009-06-18 02:50:16 +00:00
|
|
|
* shared cache (memcached). Any operation which changes the
|
2008-08-05 13:42:02 +00:00
|
|
|
* corresponding database fields must call a cache-clearing function.
|
|
|
|
|
* @showinitializer
|
2019-06-05 17:19:22 +00:00
|
|
|
* @var string[]
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
protected static $mCacheVars = [
|
2008-08-05 13:42:02 +00:00
|
|
|
// user table
|
2006-10-14 06:58:19 +00:00
|
|
|
'mId',
|
|
|
|
|
'mName',
|
|
|
|
|
'mRealName',
|
|
|
|
|
'mEmail',
|
|
|
|
|
'mTouched',
|
|
|
|
|
'mToken',
|
|
|
|
|
'mEmailAuthenticated',
|
|
|
|
|
'mEmailToken',
|
|
|
|
|
'mEmailTokenExpires',
|
|
|
|
|
'mRegistration',
|
2017-09-12 17:12:29 +00:00
|
|
|
// actor table
|
|
|
|
|
'mActorId',
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2015-07-13 21:18:55 +00:00
|
|
|
/** Cache variables */
|
2020-09-27 02:08:38 +00:00
|
|
|
// Some of these are public, including for use by the UserFactory, but they generally
|
|
|
|
|
// should not be set manually
|
2015-09-11 13:44:59 +00:00
|
|
|
// @{
|
2016-03-18 13:23:40 +00:00
|
|
|
/** @var int */
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mId;
|
2015-07-13 21:18:55 +00:00
|
|
|
/** @var string */
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mName;
|
2020-10-02 02:46:22 +00:00
|
|
|
/**
|
|
|
|
|
* Switched from protected to public for use in UserFactory
|
|
|
|
|
*
|
|
|
|
|
* @var int|null
|
|
|
|
|
*/
|
|
|
|
|
public $mActorId;
|
2015-07-13 21:18:55 +00:00
|
|
|
/** @var string */
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mRealName;
|
2015-09-04 16:17:42 +00:00
|
|
|
|
2015-07-13 21:18:55 +00:00
|
|
|
/** @var string */
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mEmail;
|
2015-03-26 05:19:32 +00:00
|
|
|
/** @var string TS_MW timestamp from the DB */
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mTouched;
|
2022-02-26 07:54:16 +00:00
|
|
|
/** @var string|null TS_MW timestamp from cache */
|
2015-03-26 05:19:32 +00:00
|
|
|
protected $mQuickTouched;
|
2022-02-26 07:54:16 +00:00
|
|
|
/** @var string|null */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mToken;
|
2022-02-26 07:54:16 +00:00
|
|
|
/** @var string|null */
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mEmailAuthenticated;
|
2022-02-26 07:54:16 +00:00
|
|
|
/** @var string|null */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mEmailToken;
|
2022-02-26 07:54:16 +00:00
|
|
|
/** @var string|null */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mEmailTokenExpires;
|
2022-02-26 07:54:16 +00:00
|
|
|
/** @var string|null */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mRegistration;
|
2015-09-11 13:44:59 +00:00
|
|
|
// @}
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2019-06-05 17:19:22 +00:00
|
|
|
// @{
|
2011-06-15 21:23:47 +00:00
|
|
|
/**
|
2019-06-05 17:19:22 +00:00
|
|
|
* @var array|bool Array with already loaded items or true if all items have been loaded.
|
2011-06-15 21:23:47 +00:00
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
protected $mLoadedItems = [];
|
2015-09-11 13:44:59 +00:00
|
|
|
// @}
|
2006-07-26 07:15:39 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2019-06-05 17:19:22 +00:00
|
|
|
* @var string Initialization data source if mLoadedItems!==true. May be one of:
|
2008-08-05 13:42:02 +00:00
|
|
|
* - 'defaults' anonymous user initialised from class defaults
|
|
|
|
|
* - 'name' initialise from mName
|
|
|
|
|
* - 'id' initialise from mId
|
2017-09-12 17:12:29 +00:00
|
|
|
* - 'actor' initialise from mActorId
|
2016-02-01 20:44:03 +00:00
|
|
|
* - 'session' log in from session if possible
|
2006-10-14 06:58:19 +00:00
|
|
|
*
|
|
|
|
|
* Use the User::newFrom*() family of functions to set this.
|
|
|
|
|
*/
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mFrom;
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2011-01-06 15:55:56 +00:00
|
|
|
/**
|
2011-01-26 17:48:58 +00:00
|
|
|
* Lazy-initialized variables, invalidated with clearInstanceCache
|
2011-01-06 15:55:56 +00:00
|
|
|
*/
|
2022-03-01 21:42:03 +00:00
|
|
|
/** @var string|null */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mDatePreference;
|
2019-09-18 19:49:11 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated since 1.35. Instead, use User::getBlock to get the block,
|
|
|
|
|
* then AbstractBlock::getByName to get the blocker's name; or use the
|
|
|
|
|
* GetUserBlock hook to set or unset a block.
|
|
|
|
|
* @var string|int -1 when the block is unset
|
|
|
|
|
*/
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mBlockedby;
|
2022-03-03 20:33:11 +00:00
|
|
|
/** @var string|false */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mHash;
|
2019-10-20 00:04:00 +00:00
|
|
|
/**
|
|
|
|
|
* TODO: This should be removed when User::BlockedFor
|
|
|
|
|
* and AbstractBlock::getReason are hard deprecated.
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mBlockreason;
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
/** @var AbstractBlock */
|
2016-04-13 14:24:30 +00:00
|
|
|
protected $mGlobalBlock;
|
2015-07-13 21:18:55 +00:00
|
|
|
/** @var bool */
|
2014-05-11 15:34:14 +00:00
|
|
|
protected $mLocked;
|
2019-09-18 19:49:11 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated since 1.35. Instead, use User::getBlock to get the block,
|
|
|
|
|
* then AbstractBlock::getHideName to determine whether the block hides
|
|
|
|
|
* the user; or use the GetUserBlock hook to hide or unhide a user.
|
|
|
|
|
* @var bool
|
|
|
|
|
*/
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mHideName;
|
2010-01-06 03:42:30 +00:00
|
|
|
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
/** @var WebRequest */
|
2011-07-06 16:42:16 +00:00
|
|
|
private $mRequest;
|
2011-03-07 14:45:11 +00:00
|
|
|
|
2019-09-18 19:49:11 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated since 1.35. Instead, use User::getBlock to get the block;
|
|
|
|
|
* or the GetUserBlock hook to set or unset a block.
|
|
|
|
|
* @var AbstractBlock|null
|
|
|
|
|
*/
|
2014-05-11 15:34:14 +00:00
|
|
|
public $mBlock;
|
2011-07-18 20:11:53 +00:00
|
|
|
|
2019-06-05 17:19:22 +00:00
|
|
|
/** @var AbstractBlock|bool */
|
2011-06-26 23:01:29 +00:00
|
|
|
private $mBlockedFromCreateAccount = false;
|
2011-03-07 14:45:11 +00:00
|
|
|
|
2017-08-20 11:20:59 +00:00
|
|
|
/** @var int User::READ_* constant bitfield used to load data */
|
2015-03-25 00:30:26 +00:00
|
|
|
protected $queryFlagsUsed = self::READ_NORMAL;
|
|
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
/** @var Authority|null lazy-initialized Authority of this user */
|
|
|
|
|
private $mThisAsAuthority;
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Lightweight constructor for an anonymous user.
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2020-07-16 11:46:28 +00:00
|
|
|
* @stable to call since 1.35
|
|
|
|
|
* @internal since 1.36, use the UserFactory service instead
|
|
|
|
|
*
|
|
|
|
|
* @see MediaWiki\User\UserFactory
|
2020-06-30 10:13:50 +00:00
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @see newFromName()
|
|
|
|
|
* @see newFromId()
|
2017-09-12 17:12:29 +00:00
|
|
|
* @see newFromActorId()
|
2008-08-05 13:42:02 +00:00
|
|
|
* @see newFromConfirmationCode()
|
|
|
|
|
* @see newFromSession()
|
|
|
|
|
* @see newFromRow()
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2014-02-21 09:58:36 +00:00
|
|
|
public function __construct() {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->clearInstanceCache( 'defaults' );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-01-27 04:25:24 +00:00
|
|
|
/**
|
|
|
|
|
* Returns self::LOCAL to indicate the user is associated with the local wiki.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.36
|
|
|
|
|
* @return string|false
|
|
|
|
|
*/
|
|
|
|
|
public function getWikiId() {
|
|
|
|
|
return self::LOCAL;
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-18 20:11:53 +00:00
|
|
|
/**
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string
|
2011-07-18 20:11:53 +00:00
|
|
|
*/
|
2014-02-21 09:58:36 +00:00
|
|
|
public function __toString() {
|
2021-02-02 17:21:48 +00:00
|
|
|
return $this->getName();
|
2011-03-11 23:42:53 +00:00
|
|
|
}
|
|
|
|
|
|
2019-07-10 18:46:21 +00:00
|
|
|
public function &__get( $name ) {
|
2019-04-09 06:58:04 +00:00
|
|
|
// A shortcut for $mRights deprecation phase
|
|
|
|
|
if ( $name === 'mRights' ) {
|
2021-05-20 15:14:28 +00:00
|
|
|
$copy = MediaWikiServices::getInstance()
|
|
|
|
|
->getPermissionManager()
|
|
|
|
|
->getUserPermissions( $this );
|
2019-07-10 18:46:21 +00:00
|
|
|
return $copy;
|
2020-01-17 06:21:28 +00:00
|
|
|
} elseif ( $name === 'mOptions' ) {
|
|
|
|
|
wfDeprecated( 'User::$mOptions', '1.35' );
|
2021-04-12 14:45:26 +00:00
|
|
|
$options = MediaWikiServices::getInstance()->getUserOptionsLookup()->getOptions( $this );
|
2020-01-17 06:21:28 +00:00
|
|
|
return $options;
|
2019-07-10 18:46:21 +00:00
|
|
|
} elseif ( !property_exists( $this, $name ) ) {
|
|
|
|
|
// T227688 - do not break $u->foo['bar'] = 1
|
|
|
|
|
wfLogWarning( 'tried to get non-existent property' );
|
|
|
|
|
$this->$name = null;
|
|
|
|
|
return $this->$name;
|
|
|
|
|
} else {
|
|
|
|
|
wfLogWarning( 'tried to get non-visible property' );
|
2019-09-01 14:00:35 +00:00
|
|
|
$null = null;
|
|
|
|
|
return $null;
|
2019-04-09 06:58:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function __set( $name, $value ) {
|
|
|
|
|
// A shortcut for $mRights deprecation phase, only known legitimate use was for
|
|
|
|
|
// testing purposes, other uses seem bad in principle
|
|
|
|
|
if ( $name === 'mRights' ) {
|
|
|
|
|
MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
|
|
|
|
|
$this,
|
2021-06-04 04:12:21 +00:00
|
|
|
$value ?? []
|
2019-04-09 06:58:04 +00:00
|
|
|
);
|
2020-01-17 06:21:28 +00:00
|
|
|
} elseif ( $name === 'mOptions' ) {
|
|
|
|
|
wfDeprecated( 'User::$mOptions', '1.35' );
|
Drop User methods which were deprecated since 1.35
The following User methods, deprecated in 1.35, have been removed:
- ::isIP,
- ::isIPRange,
- ::isValidUserName,
- ::isUsableName,
- ::isCreatableName,
- ::getCanonicalName,
- ::addAutopromoteOnceGroups,
- ::getDefaultOptions,
- ::getDefaultOption,
- ::getOptions,
- ::getBoolOption,
- ::getIntOption,
- ::setOption
- ::listOptionKinds
- ::getOptionKinds,
- ::resetOptions,
- ::getEffectiveGroups,
- ::getAutomaticGroups,
- ::getFormerGroups
User::GETOPTIONS_EXCLUDE_DEFAULTS has been removed, since it is used only in the description of User::getOptions.
Bug: T277511
Depends-On: Ida05c22f81b30d9b46678e8ede3d531c38855d83
Change-Id: I72bbc2336f8ddbc66ce67226cd2d5baaa2f807d8
2021-11-09 13:32:23 +00:00
|
|
|
$userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
|
|
|
|
|
$userOptionsManager->clearUserOptionsCache( $this );
|
2020-01-17 06:21:28 +00:00
|
|
|
foreach ( $value as $key => $val ) {
|
Drop User methods which were deprecated since 1.35
The following User methods, deprecated in 1.35, have been removed:
- ::isIP,
- ::isIPRange,
- ::isValidUserName,
- ::isUsableName,
- ::isCreatableName,
- ::getCanonicalName,
- ::addAutopromoteOnceGroups,
- ::getDefaultOptions,
- ::getDefaultOption,
- ::getOptions,
- ::getBoolOption,
- ::getIntOption,
- ::setOption
- ::listOptionKinds
- ::getOptionKinds,
- ::resetOptions,
- ::getEffectiveGroups,
- ::getAutomaticGroups,
- ::getFormerGroups
User::GETOPTIONS_EXCLUDE_DEFAULTS has been removed, since it is used only in the description of User::getOptions.
Bug: T277511
Depends-On: Ida05c22f81b30d9b46678e8ede3d531c38855d83
Change-Id: I72bbc2336f8ddbc66ce67226cd2d5baaa2f807d8
2021-11-09 13:32:23 +00:00
|
|
|
$userOptionsManager->setOption( $this, $key, $val );
|
2020-01-17 06:21:28 +00:00
|
|
|
}
|
2019-07-10 18:46:21 +00:00
|
|
|
} elseif ( !property_exists( $this, $name ) ) {
|
|
|
|
|
$this->$name = $value;
|
|
|
|
|
} else {
|
|
|
|
|
wfLogWarning( 'tried to set non-visible property' );
|
2019-04-09 06:58:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-09 01:09:55 +00:00
|
|
|
public function __sleep(): array {
|
|
|
|
|
return array_diff(
|
|
|
|
|
array_keys( get_object_vars( $this ) ),
|
|
|
|
|
[
|
|
|
|
|
'mThisAsAuthority' // memoization, will be recreated on demand.
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-03 20:41:00 +00:00
|
|
|
/**
|
2016-02-23 22:05:15 +00:00
|
|
|
* Test if it's safe to load this User object.
|
|
|
|
|
*
|
|
|
|
|
* You should typically check this before using $wgUser or
|
|
|
|
|
* RequestContext::getUser in a method that might be called before the
|
|
|
|
|
* system has been fully initialized. If the object is unsafe, you should
|
|
|
|
|
* use an anonymous user:
|
2016-02-06 21:04:34 +00:00
|
|
|
* \code
|
|
|
|
|
* $user = $wgUser->isSafeToLoad() ? $wgUser : new User;
|
|
|
|
|
* \endcode
|
|
|
|
|
*
|
|
|
|
|
* @since 1.27
|
2016-02-03 20:41:00 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isSafeToLoad() {
|
|
|
|
|
global $wgFullyInitialised;
|
2016-02-23 22:05:15 +00:00
|
|
|
|
|
|
|
|
// The user is safe to load if:
|
|
|
|
|
// * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data)
|
|
|
|
|
// * mLoadedItems === true (already loaded)
|
|
|
|
|
// * mFrom !== 'session' (sessions not involved at all)
|
|
|
|
|
|
|
|
|
|
return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) ||
|
|
|
|
|
$this->mLoadedItems === true || $this->mFrom !== 'session';
|
2016-02-03 20:41:00 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Load the user table data for this object from the source given by mFrom.
|
2015-03-24 00:39:48 +00:00
|
|
|
*
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags User::READ_* constant bitfield
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2015-03-29 01:01:27 +00:00
|
|
|
public function load( $flags = self::READ_NORMAL ) {
|
2016-02-01 20:44:03 +00:00
|
|
|
global $wgFullyInitialised;
|
|
|
|
|
|
2011-04-30 14:08:12 +00:00
|
|
|
if ( $this->mLoadedItems === true ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2013-06-03 22:02:10 +00:00
|
|
|
// Set it now to avoid infinite recursion in accessors
|
2016-02-01 20:44:03 +00:00
|
|
|
$oldLoadedItems = $this->mLoadedItems;
|
2011-04-30 14:08:12 +00:00
|
|
|
$this->mLoadedItems = true;
|
2015-03-25 00:30:26 +00:00
|
|
|
$this->queryFlagsUsed = $flags;
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2016-02-01 20:44:03 +00:00
|
|
|
// If this is called too early, things are likely to break.
|
2016-02-03 20:41:00 +00:00
|
|
|
if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
|
2021-03-15 15:25:33 +00:00
|
|
|
LoggerFactory::getInstance( 'session' )
|
2016-02-17 09:09:32 +00:00
|
|
|
->warning( 'User::loadFromSession called before the end of Setup.php', [
|
2016-02-01 20:44:03 +00:00
|
|
|
'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2016-02-01 20:44:03 +00:00
|
|
|
$this->loadDefaults();
|
|
|
|
|
$this->mLoadedItems = $oldLoadedItems;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
switch ( $this->mFrom ) {
|
|
|
|
|
case 'defaults':
|
|
|
|
|
$this->loadDefaults();
|
|
|
|
|
break;
|
|
|
|
|
case 'id':
|
2018-02-23 18:25:10 +00:00
|
|
|
// Make sure this thread sees its own changes, if the ID isn't 0
|
|
|
|
|
if ( $this->mId != 0 ) {
|
|
|
|
|
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
2021-09-02 19:35:05 +00:00
|
|
|
if ( $lb->hasOrMadeRecentPrimaryChanges() ) {
|
2018-02-23 18:25:10 +00:00
|
|
|
$flags |= self::READ_LATEST;
|
|
|
|
|
$this->queryFlagsUsed = $flags;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-24 00:39:48 +00:00
|
|
|
$this->loadFromId( $flags );
|
2006-10-14 06:58:19 +00:00
|
|
|
break;
|
2017-09-12 17:12:29 +00:00
|
|
|
case 'actor':
|
2019-07-23 17:40:52 +00:00
|
|
|
case 'name':
|
2017-09-12 17:12:29 +00:00
|
|
|
// Make sure this thread sees its own changes
|
2018-05-02 20:12:12 +00:00
|
|
|
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
2021-09-02 19:35:05 +00:00
|
|
|
if ( $lb->hasOrMadeRecentPrimaryChanges() ) {
|
2017-09-12 17:12:29 +00:00
|
|
|
$flags |= self::READ_LATEST;
|
|
|
|
|
$this->queryFlagsUsed = $flags;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
|
|
|
|
|
$row = wfGetDB( $index )->selectRow(
|
|
|
|
|
'actor',
|
2019-07-23 17:40:52 +00:00
|
|
|
[ 'actor_id', 'actor_user', 'actor_name' ],
|
2021-01-26 04:22:57 +00:00
|
|
|
$this->mFrom === 'name'
|
|
|
|
|
// make sure to use normalized form of IP for anonymous users
|
|
|
|
|
? [ 'actor_name' => IPUtils::sanitizeIP( $this->mName ) ]
|
|
|
|
|
: [ 'actor_id' => $this->mActorId ],
|
2017-09-12 17:12:29 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
$options
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !$row ) {
|
|
|
|
|
// Ugh.
|
2019-07-23 17:40:52 +00:00
|
|
|
$this->loadDefaults( $this->mFrom === 'name' ? $this->mName : false );
|
2017-09-12 17:12:29 +00:00
|
|
|
} elseif ( $row->actor_user ) {
|
|
|
|
|
$this->mId = $row->actor_user;
|
|
|
|
|
$this->loadFromId( $flags );
|
|
|
|
|
} else {
|
2019-07-23 17:40:52 +00:00
|
|
|
$this->loadDefaults( $row->actor_name, $row->actor_id );
|
2017-09-12 17:12:29 +00:00
|
|
|
}
|
|
|
|
|
break;
|
2006-10-14 06:58:19 +00:00
|
|
|
case 'session':
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( !$this->loadFromSession() ) {
|
2012-08-27 02:28:48 +00:00
|
|
|
// Loading from session failed. Load defaults.
|
|
|
|
|
$this->loadDefaults();
|
|
|
|
|
}
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserLoadAfterLoadFromSession( $this );
|
2006-10-14 06:58:19 +00:00
|
|
|
break;
|
|
|
|
|
default:
|
2015-06-25 20:02:18 +00:00
|
|
|
throw new UnexpectedValueException(
|
|
|
|
|
"Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
|
2005-07-01 21:47:23 +00:00
|
|
|
}
|
2006-10-14 06:58:19 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Load user table data, given mId has already been set.
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags User::READ_* constant bitfield
|
2014-07-24 17:42:24 +00:00
|
|
|
* @return bool False if the ID does not exist, true otherwise
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2015-06-25 20:03:43 +00:00
|
|
|
public function loadFromId( $flags = self::READ_NORMAL ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
if ( $this->mId == 0 ) {
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
// Anonymous users are not in the database (don't need cache)
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->loadDefaults();
|
|
|
|
|
return false;
|
2008-04-14 07:45:50 +00:00
|
|
|
}
|
2005-07-24 09:48:14 +00:00
|
|
|
|
2021-05-14 20:04:02 +00:00
|
|
|
// Try cache (unless this needs data from the primary DB).
|
2015-03-29 01:01:27 +00:00
|
|
|
// NOTE: if this thread called saveSettings(), the cache was cleared.
|
2015-09-30 20:39:54 +00:00
|
|
|
$latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
if ( $latest ) {
|
2015-03-24 00:39:48 +00:00
|
|
|
if ( !$this->loadFromDatabase( $flags ) ) {
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
// Can't load from ID
|
2006-10-14 06:58:19 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
} else {
|
|
|
|
|
$this->loadFromCache();
|
2006-10-14 06:58:19 +00:00
|
|
|
}
|
2013-01-01 17:44:10 +00:00
|
|
|
|
|
|
|
|
$this->mLoadedItems = true;
|
2015-03-25 00:30:26 +00:00
|
|
|
$this->queryFlagsUsed = $flags;
|
2013-01-01 17:44:10 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-27 23:43:40 +00:00
|
|
|
/**
|
|
|
|
|
* @since 1.27
|
2019-06-28 01:23:18 +00:00
|
|
|
* @param string $dbDomain
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $userId
|
2015-10-27 23:43:40 +00:00
|
|
|
*/
|
2019-06-28 01:23:18 +00:00
|
|
|
public static function purge( $dbDomain, $userId ) {
|
2018-10-15 22:20:50 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
2019-06-28 01:23:18 +00:00
|
|
|
$key = $cache->makeGlobalKey( 'user', 'id', $dbDomain, $userId );
|
2016-02-29 23:41:56 +00:00
|
|
|
$cache->delete( $key );
|
2015-10-27 23:43:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @since 1.27
|
|
|
|
|
* @param WANObjectCache $cache
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
protected function getCacheKey( WANObjectCache $cache ) {
|
2018-10-15 22:20:50 +00:00
|
|
|
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
|
|
|
|
|
|
|
|
|
|
return $cache->makeGlobalKey( 'user', 'id', $lbFactory->getLocalDomainID(), $this->mId );
|
2015-10-27 23:43:40 +00:00
|
|
|
}
|
|
|
|
|
|
2016-09-03 04:43:16 +00:00
|
|
|
/**
|
|
|
|
|
* @param WANObjectCache $cache
|
|
|
|
|
* @return string[]
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function getMutableCacheKeys( WANObjectCache $cache ) {
|
|
|
|
|
$id = $this->getId();
|
|
|
|
|
|
|
|
|
|
return $id ? [ $this->getCacheKey( $cache ) ] : [];
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-09 14:15:13 +00:00
|
|
|
/**
|
|
|
|
|
* Load user data from shared cache, given mId has already been set.
|
|
|
|
|
*
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
* @return bool True
|
2013-12-09 14:15:13 +00:00
|
|
|
* @since 1.25
|
|
|
|
|
*/
|
2015-03-25 00:30:26 +00:00
|
|
|
protected function loadFromCache() {
|
2019-10-24 03:14:31 +00:00
|
|
|
global $wgFullyInitialised;
|
|
|
|
|
|
2019-02-07 03:30:57 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
$data = $cache->getWithSetCallback(
|
|
|
|
|
$this->getCacheKey( $cache ),
|
|
|
|
|
$cache::TTL_HOUR,
|
2019-10-24 03:14:31 +00:00
|
|
|
function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache, $wgFullyInitialised ) {
|
2016-09-05 19:55:19 +00:00
|
|
|
$setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
|
2020-06-01 05:00:39 +00:00
|
|
|
wfDebug( "User: cache miss for user {$this->mId}" );
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
|
2016-06-09 07:15:20 +00:00
|
|
|
$this->loadFromDatabase( self::READ_NORMAL );
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
|
|
|
|
|
$data = [];
|
|
|
|
|
foreach ( self::$mCacheVars as $name ) {
|
|
|
|
|
$data[$name] = $this->$name;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 21:47:01 +00:00
|
|
|
$ttl = $cache->adaptiveTTL( (int)wfTimestamp( TS_UNIX, $this->mTouched ), $ttl );
|
2016-08-21 21:53:55 +00:00
|
|
|
|
2019-10-24 03:14:31 +00:00
|
|
|
if ( $wgFullyInitialised ) {
|
|
|
|
|
$groupMemberships = MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
|
|
|
|
->getUserGroupMemberships( $this, $this->queryFlagsUsed );
|
|
|
|
|
|
|
|
|
|
// if a user group membership is about to expire, the cache needs to
|
|
|
|
|
// expire at that time (T163691)
|
|
|
|
|
foreach ( $groupMemberships as $ugm ) {
|
|
|
|
|
if ( $ugm->getExpiry() ) {
|
|
|
|
|
$secondsUntilExpiry =
|
2021-10-16 21:47:01 +00:00
|
|
|
(int)wfTimestamp( TS_UNIX, $ugm->getExpiry() ) - time();
|
2019-10-24 03:14:31 +00:00
|
|
|
|
|
|
|
|
if ( $secondsUntilExpiry > 0 && $secondsUntilExpiry < $ttl ) {
|
|
|
|
|
$ttl = $secondsUntilExpiry;
|
|
|
|
|
}
|
2017-04-25 03:55:26 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
return $data;
|
|
|
|
|
},
|
|
|
|
|
[ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ]
|
|
|
|
|
);
|
2013-12-09 14:15:13 +00:00
|
|
|
|
|
|
|
|
// Restore from cache
|
|
|
|
|
foreach ( self::$mCacheVars as $name ) {
|
|
|
|
|
$this->$name = $data[$name];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Improve custom folding and grouping
PHPStorm can use custom folding regions defined in either the
VisualStudio style or the NetBeans style. The VisualStudio style is more
pleasing to the eye and also works as a vim foldmarker. So get rid of
the previous vim foldmarkers, and use region/endregion.
region/endregion need to be in a single-line comment which is not a doc
comment, and the rest of the comment is used as a region heading (by
both PHPStorm and vim). So to retain Doxygen @name tags, it is
necessary to repeat the section heading, once in a @name and once in a
region. Establish a standard style for this, with a divider and three
spaces before the heading, to better set off the heading name in plain
text.
Besides being the previous vim foldmarker, @{ is also a Doxygen
grouping command. However, almost all prior usages of @{ ... @} in this
sense were broken for one reason or another. It's necessary for the @{
to be in a doc comment, and DISTRIBUTE_GROUP_DOC doesn't work if any of
the individual members in the group are separately documented.
@name alone is sufficient to create a Doxygen section when the sections
are adjacent, but if there is ungrouped content after the section, it
is necessary to use @{ ... @} to avoid having the Doxygen group run on.
So I retained, fixed or added @{ ... @} in certain cases.
I wasn't able to test the changes to the trait documentation in Doxygen
since trait syntax is not recognised and the output is badly broken.
Change-Id: I7d819fdb376c861f40bfc01aed74cd3706141b20
2020-12-22 23:52:00 +00:00
|
|
|
/***************************************************************************/
|
|
|
|
|
// region newFrom*() static factory methods
|
|
|
|
|
/** @name newFrom*() static factory methods
|
|
|
|
|
* @{
|
|
|
|
|
*/
|
2007-10-03 08:46:17 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2020-09-27 02:08:38 +00:00
|
|
|
* @see UserFactory::newFromName
|
2006-10-14 06:58:19 +00:00
|
|
|
*
|
2021-03-12 18:59:12 +00:00
|
|
|
* @deprecated since 1.36, use a UserFactory instead
|
|
|
|
|
*
|
2006-10-14 06:58:19 +00:00
|
|
|
* This is slightly less efficient than newFromId(), so use newFromId() if
|
2008-04-14 07:45:50 +00:00
|
|
|
* you have both an ID and a name handy.
|
2006-10-14 06:58:19 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $name Username, validated by Title::newFromText()
|
Drop User methods which were deprecated since 1.35
The following User methods, deprecated in 1.35, have been removed:
- ::isIP,
- ::isIPRange,
- ::isValidUserName,
- ::isUsableName,
- ::isCreatableName,
- ::getCanonicalName,
- ::addAutopromoteOnceGroups,
- ::getDefaultOptions,
- ::getDefaultOption,
- ::getOptions,
- ::getBoolOption,
- ::getIntOption,
- ::setOption
- ::listOptionKinds
- ::getOptionKinds,
- ::resetOptions,
- ::getEffectiveGroups,
- ::getAutomaticGroups,
- ::getFormerGroups
User::GETOPTIONS_EXCLUDE_DEFAULTS has been removed, since it is used only in the description of User::getOptions.
Bug: T277511
Depends-On: Ida05c22f81b30d9b46678e8ede3d531c38855d83
Change-Id: I72bbc2336f8ddbc66ce67226cd2d5baaa2f807d8
2021-11-09 13:32:23 +00:00
|
|
|
* @param string|bool $validate Validate username.Type of validation to use:
|
|
|
|
|
* - false No validation
|
|
|
|
|
* - 'valid' Valid for batch processes
|
|
|
|
|
* - 'usable' Valid for batch processes and login
|
|
|
|
|
* - 'creatable' Valid for batch processes, login and account creation,
|
|
|
|
|
* except that true is accepted as an alias for 'valid', for BC.
|
2008-04-14 07:45:50 +00:00
|
|
|
*
|
2012-11-28 19:37:05 +00:00
|
|
|
* @return User|bool User object, or false if the username is invalid
|
2013-06-03 22:02:10 +00:00
|
|
|
* (e.g. if it contains illegal characters or is an IP address). If the
|
|
|
|
|
* username is not present in the database, the result will be a user object
|
|
|
|
|
* with a name, zero user ID and default settings.
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public static function newFromName( $name, $validate = 'valid' ) {
|
2020-09-27 02:08:38 +00:00
|
|
|
// Backwards compatibility with strings / false
|
|
|
|
|
$validationLevels = [
|
2020-09-29 17:04:22 +00:00
|
|
|
'valid' => UserFactory::RIGOR_VALID,
|
|
|
|
|
'usable' => UserFactory::RIGOR_USABLE,
|
|
|
|
|
'creatable' => UserFactory::RIGOR_CREATABLE
|
2020-09-27 02:08:38 +00:00
|
|
|
];
|
2006-10-14 06:58:19 +00:00
|
|
|
if ( $validate === true ) {
|
|
|
|
|
$validate = 'valid';
|
|
|
|
|
}
|
2020-09-27 02:08:38 +00:00
|
|
|
if ( $validate === false ) {
|
2020-09-29 17:04:22 +00:00
|
|
|
$validation = UserFactory::RIGOR_NONE;
|
2020-09-27 02:08:38 +00:00
|
|
|
} elseif ( array_key_exists( $validate, $validationLevels ) ) {
|
|
|
|
|
$validation = $validationLevels[ $validate ];
|
|
|
|
|
} else {
|
|
|
|
|
// Not a recognized value, probably a test for unsupported validation
|
|
|
|
|
// levels, regardless, just pass it along
|
|
|
|
|
$validation = $validate;
|
2004-09-11 06:58:47 +00:00
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
2020-09-27 02:08:38 +00:00
|
|
|
$user = MediaWikiServices::getInstance()
|
|
|
|
|
->getUserFactory()
|
|
|
|
|
->newFromName( (string)$name, $validation );
|
2019-02-07 03:28:29 +00:00
|
|
|
|
2020-09-27 02:08:38 +00:00
|
|
|
// UserFactory returns null instead of false
|
|
|
|
|
if ( $user === null ) {
|
|
|
|
|
$user = false;
|
|
|
|
|
}
|
|
|
|
|
return $user;
|
2006-10-14 06:58:19 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Static factory method for creation from a given user ID.
|
|
|
|
|
*
|
2020-10-02 02:46:22 +00:00
|
|
|
* @see UserFactory::newFromId
|
|
|
|
|
*
|
2021-03-12 18:59:12 +00:00
|
|
|
* @deprecated since 1.36, use a UserFactory instead
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $id Valid user ID
|
2021-06-17 14:32:05 +00:00
|
|
|
* @return User
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public static function newFromId( $id ) {
|
2020-10-02 02:46:22 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserFactory()
|
|
|
|
|
->newFromId( (int)$id );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
/**
|
|
|
|
|
* Static factory method for creation from a given actor ID.
|
|
|
|
|
*
|
2020-10-02 02:46:22 +00:00
|
|
|
* @see UserFactory::newFromActorId
|
|
|
|
|
*
|
2021-03-12 18:59:12 +00:00
|
|
|
* @deprecated since 1.36, use a UserFactory instead
|
|
|
|
|
*
|
2017-09-12 17:12:29 +00:00
|
|
|
* @since 1.31
|
|
|
|
|
* @param int $id Valid actor ID
|
2021-06-17 14:32:05 +00:00
|
|
|
* @return User
|
2017-09-12 17:12:29 +00:00
|
|
|
*/
|
|
|
|
|
public static function newFromActorId( $id ) {
|
2020-10-02 02:46:22 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserFactory()
|
|
|
|
|
->newFromActorId( (int)$id );
|
2017-09-12 17:12:29 +00:00
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
/**
|
|
|
|
|
* Returns a User object corresponding to the given UserIdentity.
|
|
|
|
|
*
|
2020-10-02 02:46:22 +00:00
|
|
|
* @see UserFactory::newFromUserIdentity
|
|
|
|
|
*
|
2021-03-12 18:59:12 +00:00
|
|
|
* @deprecated since 1.36, use a UserFactory instead
|
|
|
|
|
*
|
2018-01-27 01:48:19 +00:00
|
|
|
* @since 1.32
|
|
|
|
|
*
|
|
|
|
|
* @param UserIdentity $identity
|
|
|
|
|
*
|
|
|
|
|
* @return User
|
|
|
|
|
*/
|
|
|
|
|
public static function newFromIdentity( UserIdentity $identity ) {
|
2020-10-18 01:02:40 +00:00
|
|
|
// Don't use the service if we already have a User object,
|
|
|
|
|
// so that User::newFromIdentity calls don't break things in unit tests.
|
|
|
|
|
if ( $identity instanceof User ) {
|
|
|
|
|
return $identity;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 18:21:21 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserFactory()
|
|
|
|
|
->newFromUserIdentity( $identity );
|
2018-01-27 01:48:19 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
/**
|
|
|
|
|
* Static factory method for creation from an ID, name, and/or actor ID
|
|
|
|
|
*
|
|
|
|
|
* This does not check that the ID, name, and actor ID all correspond to
|
|
|
|
|
* the same user.
|
|
|
|
|
*
|
2020-10-02 02:46:22 +00:00
|
|
|
* @see UserFactory::newFromAnyId
|
|
|
|
|
*
|
2021-03-12 18:59:12 +00:00
|
|
|
* @deprecated since 1.36, use a UserFactory instead
|
|
|
|
|
*
|
2017-09-12 17:12:29 +00:00
|
|
|
* @since 1.31
|
|
|
|
|
* @param int|null $userId User ID, if known
|
|
|
|
|
* @param string|null $userName User name, if known
|
|
|
|
|
* @param int|null $actorId Actor ID, if known
|
2019-06-28 01:23:18 +00:00
|
|
|
* @param bool|string $dbDomain remote wiki to which the User/Actor ID applies, or false if none
|
2017-09-12 17:12:29 +00:00
|
|
|
* @return User
|
|
|
|
|
*/
|
2019-06-28 01:23:18 +00:00
|
|
|
public static function newFromAnyId( $userId, $userName, $actorId, $dbDomain = false ) {
|
2020-10-02 02:46:22 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserFactory()
|
|
|
|
|
->newFromAnyId( $userId, $userName, $actorId, $dbDomain );
|
2017-09-12 17:12:29 +00:00
|
|
|
}
|
|
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
2006-10-14 06:58:19 +00:00
|
|
|
* Factory method to fetch whichever user has a given email confirmation code.
|
2005-04-25 18:38:43 +00:00
|
|
|
* This code is generated when an account is created or its e-mail address
|
|
|
|
|
* has changed.
|
|
|
|
|
*
|
|
|
|
|
* If the code is invalid or has expired, returns NULL.
|
|
|
|
|
*
|
2020-10-02 02:46:22 +00:00
|
|
|
* @see UserFactory::newFromConfirmationCode
|
|
|
|
|
*
|
2021-03-12 18:59:12 +00:00
|
|
|
* @deprecated since 1.36, use a UserFactory instead
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $code Confirmation code
|
2015-08-03 23:20:39 +00:00
|
|
|
* @param int $flags User::READ_* bitfield
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return User|null
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2020-09-09 19:58:16 +00:00
|
|
|
public static function newFromConfirmationCode( $code, $flags = self::READ_NORMAL ) {
|
2020-10-02 02:46:22 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserFactory()
|
|
|
|
|
->newFromConfirmationCode( (string)$code, $flags );
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2005-06-25 13:41:59 +00:00
|
|
|
/**
|
2016-02-01 20:44:03 +00:00
|
|
|
* Create a new user object using data from session. If the login
|
|
|
|
|
* credentials are invalid, the result is an anonymous user.
|
2006-10-14 06:58:19 +00:00
|
|
|
*
|
2020-12-15 09:47:15 +00:00
|
|
|
* @param WebRequest|null $request Object to use; the global request will be used if omitted.
|
2014-04-23 09:41:35 +00:00
|
|
|
* @return User
|
2005-06-25 13:41:59 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public static function newFromSession( WebRequest $request = null ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
$user = new User;
|
|
|
|
|
$user->mFrom = 'session';
|
2011-07-06 16:42:16 +00:00
|
|
|
$user->mRequest = $request;
|
2006-10-14 06:58:19 +00:00
|
|
|
return $user;
|
2005-06-25 13:41:59 +00:00
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
|
2008-04-15 09:04:45 +00:00
|
|
|
/**
|
|
|
|
|
* Create a new user object from a user row.
|
2011-04-30 14:08:12 +00:00
|
|
|
* The row should have the following fields from the user table in it:
|
|
|
|
|
* - either user_name or user_id to load further data if needed (or both)
|
|
|
|
|
* - user_real_name
|
2015-09-04 16:17:42 +00:00
|
|
|
* - all other fields (email, etc.)
|
2011-04-30 14:08:12 +00:00
|
|
|
* It is useless to provide the remaining fields if either user_id,
|
|
|
|
|
* user_name and user_real_name are not provided because the whole row
|
|
|
|
|
* will be loaded once more from the database when accessing them.
|
|
|
|
|
*
|
2013-12-27 12:56:52 +00:00
|
|
|
* @param stdClass $row A row from the user table
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param array|null $data Further data to load into the object
|
|
|
|
|
* (see User::loadFromRow for valid keys)
|
2011-01-06 15:55:56 +00:00
|
|
|
* @return User
|
2008-04-15 09:04:45 +00:00
|
|
|
*/
|
2012-09-11 23:45:35 +00:00
|
|
|
public static function newFromRow( $row, $data = null ) {
|
2008-04-15 09:04:45 +00:00
|
|
|
$user = new User;
|
2012-09-11 23:45:35 +00:00
|
|
|
$user->loadFromRow( $row, $data );
|
2008-04-15 09:04:45 +00:00
|
|
|
return $user;
|
|
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2015-09-04 16:17:42 +00:00
|
|
|
/**
|
|
|
|
|
* Static factory method for creation of a "system" user from username.
|
|
|
|
|
*
|
|
|
|
|
* A "system" user is an account that's used to attribute logged actions
|
|
|
|
|
* taken by MediaWiki itself, as opposed to a bot or human user. Examples
|
|
|
|
|
* might include the 'Maintenance script' or 'Conversion script' accounts
|
|
|
|
|
* used by various scripts in the maintenance/ directory or accounts such
|
|
|
|
|
* as 'MediaWiki message delivery' used by the MassMessage extension.
|
|
|
|
|
*
|
|
|
|
|
* This can optionally create the user if it doesn't exist, and "steal" the
|
|
|
|
|
* account if it does exist.
|
|
|
|
|
*
|
2016-05-24 15:05:47 +00:00
|
|
|
* "Stealing" an existing user is intended to make it impossible for normal
|
|
|
|
|
* authentication processes to use the account, effectively disabling the
|
|
|
|
|
* account for normal use:
|
|
|
|
|
* - Email is invalidated, to prevent account recovery by emailing a
|
|
|
|
|
* temporary password and to disassociate the account from the existing
|
|
|
|
|
* human.
|
|
|
|
|
* - The token is set to a magic invalid value, to kill existing sessions
|
|
|
|
|
* and to prevent $this->setToken() calls from resetting the token to a
|
|
|
|
|
* valid value.
|
|
|
|
|
* - SessionManager is instructed to prevent new sessions for the user, to
|
|
|
|
|
* do things like deauthorizing OAuth consumers.
|
|
|
|
|
* - AuthManager is instructed to revoke access, to invalidate or remove
|
|
|
|
|
* passwords and other credentials.
|
|
|
|
|
*
|
2015-09-04 16:17:42 +00:00
|
|
|
* @param string $name Username
|
|
|
|
|
* @param array $options Options are:
|
Drop User methods which were deprecated since 1.35
The following User methods, deprecated in 1.35, have been removed:
- ::isIP,
- ::isIPRange,
- ::isValidUserName,
- ::isUsableName,
- ::isCreatableName,
- ::getCanonicalName,
- ::addAutopromoteOnceGroups,
- ::getDefaultOptions,
- ::getDefaultOption,
- ::getOptions,
- ::getBoolOption,
- ::getIntOption,
- ::setOption
- ::listOptionKinds
- ::getOptionKinds,
- ::resetOptions,
- ::getEffectiveGroups,
- ::getAutomaticGroups,
- ::getFormerGroups
User::GETOPTIONS_EXCLUDE_DEFAULTS has been removed, since it is used only in the description of User::getOptions.
Bug: T277511
Depends-On: Ida05c22f81b30d9b46678e8ede3d531c38855d83
Change-Id: I72bbc2336f8ddbc66ce67226cd2d5baaa2f807d8
2021-11-09 13:32:23 +00:00
|
|
|
* - validate: Type of validation to use:
|
|
|
|
|
* - false No validation
|
|
|
|
|
* - 'valid' Valid for batch processes
|
|
|
|
|
* - 'usable' Valid for batch processes and login
|
|
|
|
|
* - 'creatable' Valid for batch processes, login and account creation,
|
|
|
|
|
* default 'valid'. Deprecated since 1.36.
|
2015-09-04 16:17:42 +00:00
|
|
|
* - create: Whether to create the user if it doesn't already exist, default true
|
2016-05-24 15:05:47 +00:00
|
|
|
* - steal: Whether to "disable" the account for normal use if it already
|
|
|
|
|
* exists, default false
|
2015-09-04 16:17:42 +00:00
|
|
|
* @return User|null
|
2016-05-14 08:06:55 +00:00
|
|
|
* @since 1.27
|
2015-09-04 16:17:42 +00:00
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
public static function newSystemUser( $name, $options = [] ) {
|
|
|
|
|
$options += [
|
2020-11-23 00:37:17 +00:00
|
|
|
'validate' => UserNameUtils::RIGOR_VALID,
|
2015-09-04 16:17:42 +00:00
|
|
|
'create' => true,
|
|
|
|
|
'steal' => false,
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2015-09-04 16:17:42 +00:00
|
|
|
|
2020-11-23 00:37:17 +00:00
|
|
|
// Username validation
|
|
|
|
|
// Backwards compatibility with strings / false
|
|
|
|
|
$validationLevels = [
|
|
|
|
|
'valid' => UserNameUtils::RIGOR_VALID,
|
|
|
|
|
'usable' => UserNameUtils::RIGOR_USABLE,
|
|
|
|
|
'creatable' => UserNameUtils::RIGOR_CREATABLE
|
|
|
|
|
];
|
|
|
|
|
$validate = $options['validate'];
|
|
|
|
|
|
|
|
|
|
// @phan-suppress-next-line PhanSuspiciousValueComparison
|
|
|
|
|
if ( $validate === false ) {
|
|
|
|
|
$validation = UserNameUtils::RIGOR_NONE;
|
|
|
|
|
} elseif ( array_key_exists( $validate, $validationLevels ) ) {
|
|
|
|
|
$validation = $validationLevels[ $validate ];
|
|
|
|
|
} else {
|
|
|
|
|
// Not a recognized value, probably a test for unsupported validation
|
|
|
|
|
// levels, regardless, just pass it along
|
|
|
|
|
$validation = $validate;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 17:09:22 +00:00
|
|
|
if ( $validation !== UserNameUtils::RIGOR_VALID ) {
|
|
|
|
|
wfDeprecatedMsg(
|
|
|
|
|
__METHOD__ . ' options["validation"] parameter must be omitted or set to "valid".',
|
|
|
|
|
'1.36'
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-11-23 00:37:17 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
|
$userNameUtils = $services->getUserNameUtils();
|
|
|
|
|
|
|
|
|
|
$name = $userNameUtils->getCanonical( (string)$name, $validation );
|
2015-09-04 16:17:42 +00:00
|
|
|
if ( $name === false ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 00:37:17 +00:00
|
|
|
$loadBalancer = $services->getDBLoadBalancer();
|
|
|
|
|
$dbr = $loadBalancer->getConnectionRef( DB_REPLICA );
|
|
|
|
|
|
2017-10-06 17:03:55 +00:00
|
|
|
$userQuery = self::getQueryInfo();
|
2017-05-14 23:56:04 +00:00
|
|
|
$row = $dbr->selectRow(
|
2017-10-06 17:03:55 +00:00
|
|
|
$userQuery['tables'],
|
|
|
|
|
$userQuery['fields'],
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 'user_name' => $name ],
|
2017-10-06 17:03:55 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$userQuery['joins']
|
2015-09-04 16:17:42 +00:00
|
|
|
);
|
2017-05-14 23:56:04 +00:00
|
|
|
if ( !$row ) {
|
2021-05-14 20:04:02 +00:00
|
|
|
// Try the primary database...
|
2021-04-29 02:37:11 +00:00
|
|
|
$dbw = $loadBalancer->getConnectionRef( DB_PRIMARY );
|
2017-05-14 23:56:04 +00:00
|
|
|
$row = $dbw->selectRow(
|
2017-10-06 17:03:55 +00:00
|
|
|
$userQuery['tables'],
|
|
|
|
|
$userQuery['fields'],
|
2017-05-14 23:56:04 +00:00
|
|
|
[ 'user_name' => $name ],
|
2017-10-06 17:03:55 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$userQuery['joins']
|
2017-05-14 23:56:04 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-04 16:17:42 +00:00
|
|
|
if ( !$row ) {
|
|
|
|
|
// No user. Create it?
|
2019-10-28 15:51:05 +00:00
|
|
|
if ( !$options['create'] ) {
|
|
|
|
|
// No.
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If it's a reserved user that had an anonymous actor created for it at
|
|
|
|
|
// some point, we need special handling.
|
2021-04-29 16:24:12 +00:00
|
|
|
return self::insertNewUser( static function ( UserIdentity $actor, IDatabase $dbw ) {
|
2021-04-07 17:32:17 +00:00
|
|
|
return MediaWikiServices::getInstance()->getActorStore()->acquireSystemActorId( $actor, $dbw );
|
|
|
|
|
}, $name, [ 'token' => self::INVALID_TOKEN ] );
|
2015-09-04 16:17:42 +00:00
|
|
|
}
|
2017-05-14 23:56:04 +00:00
|
|
|
|
2015-09-04 16:17:42 +00:00
|
|
|
$user = self::newFromRow( $row );
|
|
|
|
|
|
2019-11-05 22:42:09 +00:00
|
|
|
if ( !$user->isSystemUser() ) {
|
2015-09-04 16:17:42 +00:00
|
|
|
// User exists. Steal it?
|
|
|
|
|
if ( !$options['steal'] ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 00:37:17 +00:00
|
|
|
$services->getAuthManager()->revokeAccessForUser( $name );
|
2015-09-04 16:17:42 +00:00
|
|
|
|
|
|
|
|
$user->invalidateEmail();
|
2016-02-01 21:59:27 +00:00
|
|
|
$user->mToken = self::INVALID_TOKEN;
|
2015-09-04 16:17:42 +00:00
|
|
|
$user->saveSettings();
|
2016-02-01 21:59:27 +00:00
|
|
|
SessionManager::singleton()->preventSessionsForUser( $user->getName() );
|
2015-09-04 16:17:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $user;
|
|
|
|
|
}
|
|
|
|
|
|
Improve custom folding and grouping
PHPStorm can use custom folding regions defined in either the
VisualStudio style or the NetBeans style. The VisualStudio style is more
pleasing to the eye and also works as a vim foldmarker. So get rid of
the previous vim foldmarkers, and use region/endregion.
region/endregion need to be in a single-line comment which is not a doc
comment, and the rest of the comment is used as a region heading (by
both PHPStorm and vim). So to retain Doxygen @name tags, it is
necessary to repeat the section heading, once in a @name and once in a
region. Establish a standard style for this, with a divider and three
spaces before the heading, to better set off the heading name in plain
text.
Besides being the previous vim foldmarker, @{ is also a Doxygen
grouping command. However, almost all prior usages of @{ ... @} in this
sense were broken for one reason or another. It's necessary for the @{
to be in a doc comment, and DISTRIBUTE_GROUP_DOC doesn't work if any of
the individual members in the group are separately documented.
@name alone is sufficient to create a Doxygen section when the sections
are adjacent, but if there is ungrouped content after the section, it
is necessary to use @{ ... @} to avoid having the Doxygen group run on.
So I retained, fixed or added @{ ... @} in certain cases.
I wasn't able to test the changes to the trait documentation in Doxygen
since trait syntax is not recognised and the output is badly broken.
Change-Id: I7d819fdb376c861f40bfc01aed74cd3706141b20
2020-12-22 23:52:00 +00:00
|
|
|
/** @} */
|
|
|
|
|
// endregion -- end of newFrom*() static factory methods
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the username corresponding to a given user ID
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $id User ID
|
2021-06-28 20:03:47 +00:00
|
|
|
* @return string|false The corresponding username
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2011-10-21 23:20:52 +00:00
|
|
|
public static function whoIs( $id ) {
|
2012-05-19 20:41:41 +00:00
|
|
|
return UserCache::singleton()->getProp( $id, 'name' );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the real name of a user given their user ID
|
2007-08-14 01:17:08 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $id User ID
|
2021-06-28 20:03:47 +00:00
|
|
|
* @return string|false The corresponding user's real name
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public static function whoIsReal( $id ) {
|
2012-05-19 20:41:41 +00:00
|
|
|
return UserCache::singleton()->getProp( $id, 'real_name' );
|
2004-04-23 22:34:33 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2004-09-04 13:06:25 +00:00
|
|
|
* Get database id given a user name
|
2021-04-06 20:53:56 +00:00
|
|
|
* @deprecated since 1.37. Use UserIdentityLookup::getUserIdentityByName instead.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $name Username
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags User::READ_* constant bitfield
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return int|null The corresponding user's ID, or null if user is nonexistent
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2015-03-29 01:01:27 +00:00
|
|
|
public static function idFromName( $name, $flags = self::READ_NORMAL ) {
|
2021-04-06 20:53:56 +00:00
|
|
|
$actor = MediaWikiServices::getInstance()
|
|
|
|
|
->getUserIdentityLookup()
|
|
|
|
|
->getUserIdentityByName( (string)$name, $flags );
|
|
|
|
|
if ( $actor && $actor->getId() ) {
|
|
|
|
|
return $actor->getId();
|
2009-06-26 14:28:25 +00:00
|
|
|
}
|
2021-04-06 20:53:56 +00:00
|
|
|
return null;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2003-04-16 07:30:52 +00:00
|
|
|
|
2016-07-27 01:44:53 +00:00
|
|
|
/**
|
|
|
|
|
* Return the users who are members of the given group(s). In case of multiple groups,
|
|
|
|
|
* users who are members of at least one of them are returned.
|
|
|
|
|
*
|
|
|
|
|
* @param string|array $groups A single group name or an array of group names
|
|
|
|
|
* @param int $limit Max number of users to return. The actual limit will never exceed 5000
|
|
|
|
|
* records; larger values are ignored.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param int|null $after ID the user to start after
|
2021-03-14 06:25:18 +00:00
|
|
|
* @return UserArrayFromResult|ArrayIterator
|
2016-07-27 01:44:53 +00:00
|
|
|
*/
|
|
|
|
|
public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) {
|
|
|
|
|
if ( $groups === [] ) {
|
|
|
|
|
return UserArrayFromResult::newFromIDs( [] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$groups = array_unique( (array)$groups );
|
|
|
|
|
$limit = min( 5000, $limit );
|
|
|
|
|
|
|
|
|
|
$conds = [ 'ug_group' => $groups ];
|
|
|
|
|
if ( $after !== null ) {
|
|
|
|
|
$conds[] = 'ug_user > ' . (int)$after;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-05 19:55:19 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2016-07-27 01:44:53 +00:00
|
|
|
$ids = $dbr->selectFieldValues(
|
|
|
|
|
'user_groups',
|
|
|
|
|
'ug_user',
|
|
|
|
|
$conds,
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[
|
|
|
|
|
'DISTINCT' => true,
|
|
|
|
|
'ORDER BY' => 'ug_user',
|
|
|
|
|
'LIMIT' => $limit,
|
|
|
|
|
]
|
|
|
|
|
) ?: [];
|
|
|
|
|
return UserArray::newFromIDs( $ids );
|
|
|
|
|
}
|
|
|
|
|
|
2005-06-27 06:33:45 +00:00
|
|
|
/**
|
2007-07-17 16:44:40 +00:00
|
|
|
* Is the input a valid password for this user?
|
2005-06-27 06:33:45 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $password Desired password
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2005-06-27 06:33:45 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isValidPassword( $password ) {
|
2018-12-20 22:44:04 +00:00
|
|
|
// simple boolean wrapper for checkPasswordValidity
|
|
|
|
|
return $this->checkPasswordValidity( $password )->isGood();
|
2009-10-19 03:01:11 +00:00
|
|
|
}
|
2014-03-12 01:47:29 +00:00
|
|
|
|
|
|
|
|
/**
|
2014-12-26 16:29:15 +00:00
|
|
|
* Check if this is a valid password for this user
|
|
|
|
|
*
|
2019-01-02 06:17:30 +00:00
|
|
|
* Returns a Status object with a set of messages describing
|
|
|
|
|
* problems with the password. If the return status is fatal,
|
|
|
|
|
* the action should be refused and the password should not be
|
|
|
|
|
* checked at all (this is mainly meant for DoS mitigation).
|
|
|
|
|
* If the return value is OK but not good, the password can be checked,
|
|
|
|
|
* but the user should not be able to set their password to this.
|
|
|
|
|
* The value of the returned Status object will be an array which
|
|
|
|
|
* can have the following fields:
|
|
|
|
|
* - forceChange (bool): if set to true, the user should not be
|
|
|
|
|
* allowed to log with this password unless they change it during
|
|
|
|
|
* the login process (see ResetPasswordSecondaryAuthenticationProvider).
|
2019-02-20 06:02:33 +00:00
|
|
|
* - suggestChangeOnLogin (bool): if set to true, the user should be prompted for
|
|
|
|
|
* a password change on login.
|
2014-03-12 01:47:29 +00:00
|
|
|
*
|
|
|
|
|
* @param string $password Desired password
|
|
|
|
|
* @return Status
|
|
|
|
|
* @since 1.23
|
|
|
|
|
*/
|
2016-12-01 23:30:23 +00:00
|
|
|
public function checkPasswordValidity( $password ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$passwordPolicy = MediaWikiServices::getInstance()->getMainConfig()->get( 'PasswordPolicy' );
|
2011-01-26 17:48:58 +00:00
|
|
|
|
2015-04-23 01:48:48 +00:00
|
|
|
$upp = new UserPasswordPolicy(
|
2022-01-06 18:44:56 +00:00
|
|
|
$passwordPolicy['policies'],
|
|
|
|
|
$passwordPolicy['checks']
|
2010-12-26 22:55:32 +00:00
|
|
|
);
|
2010-01-06 03:42:30 +00:00
|
|
|
|
2019-01-02 06:17:30 +00:00
|
|
|
$status = Status::newGood( [] );
|
2015-09-11 13:44:59 +00:00
|
|
|
$result = false; // init $result to false for the internal checks
|
2010-01-06 03:42:30 +00:00
|
|
|
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
if ( !$this->getHookRunner()->onIsValidPassword( $password, $result, $this ) ) {
|
2014-03-12 01:47:29 +00:00
|
|
|
$status->error( $result );
|
|
|
|
|
return $status;
|
2013-04-20 22:49:30 +00:00
|
|
|
}
|
2010-01-06 03:42:30 +00:00
|
|
|
|
2009-10-28 17:53:36 +00:00
|
|
|
if ( $result === false ) {
|
2019-01-02 06:17:30 +00:00
|
|
|
$status->merge( $upp->checkUserPassword( $this, $password ), true );
|
2015-04-23 01:48:48 +00:00
|
|
|
return $status;
|
2019-02-07 03:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $result === true ) {
|
2014-03-12 01:47:29 +00:00
|
|
|
return $status;
|
2009-06-27 16:53:27 +00:00
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
$status->error( $result );
|
|
|
|
|
return $status; // the isValidPassword hook set a string $result and returned true
|
2005-06-27 06:33:45 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2009-06-18 02:50:16 +00:00
|
|
|
* Set cached properties to default.
|
2006-10-14 06:58:19 +00:00
|
|
|
*
|
2009-06-18 02:50:16 +00:00
|
|
|
* @note This no longer clears uncached lazy-initialised properties;
|
2008-08-05 13:42:02 +00:00
|
|
|
* the constructor does that instead.
|
2011-07-18 20:11:53 +00:00
|
|
|
*
|
2014-04-23 09:41:35 +00:00
|
|
|
* @param string|bool $name
|
2019-07-23 17:40:52 +00:00
|
|
|
* @param int|null $actorId
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2019-07-23 17:40:52 +00:00
|
|
|
public function loadDefaults( $name = false, $actorId = null ) {
|
2004-10-23 08:26:48 +00:00
|
|
|
$this->mId = 0;
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->mName = $name;
|
2019-07-23 17:40:52 +00:00
|
|
|
$this->mActorId = $actorId;
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->mRealName = '';
|
|
|
|
|
$this->mEmail = '';
|
2005-01-18 03:06:20 +00:00
|
|
|
|
2016-02-18 20:56:40 +00:00
|
|
|
$loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' )
|
|
|
|
|
? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0;
|
2016-02-01 20:44:03 +00:00
|
|
|
if ( $loggedOut !== 0 ) {
|
2011-07-06 16:42:16 +00:00
|
|
|
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
|
2006-10-14 06:58:19 +00:00
|
|
|
} else {
|
2012-10-09 22:20:05 +00:00
|
|
|
$this->mTouched = '1'; # Allow any pages to be cached
|
2005-01-18 03:06:20 +00:00
|
|
|
}
|
|
|
|
|
|
2012-03-20 05:17:40 +00:00
|
|
|
$this->mToken = null; // Don't run cryptographic functions till we need a token
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->mEmailAuthenticated = null;
|
|
|
|
|
$this->mEmailToken = '';
|
|
|
|
|
$this->mEmailTokenExpires = null;
|
2005-12-22 05:41:06 +00:00
|
|
|
$this->mRegistration = wfTimestamp( TS_MW );
|
2006-01-07 13:31:29 +00:00
|
|
|
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserLoadDefaults( $this, $name );
|
2006-10-14 06:58:19 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2011-04-30 14:08:12 +00:00
|
|
|
/**
|
|
|
|
|
* Return whether an item has been loaded.
|
|
|
|
|
*
|
2014-04-23 09:41:35 +00:00
|
|
|
* @param string $item Item to check. Current possibilities:
|
|
|
|
|
* - id
|
|
|
|
|
* - name
|
|
|
|
|
* - realname
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $all 'all' to check if the whole object has been loaded
|
2014-04-23 09:41:35 +00:00
|
|
|
* or any other string to check if only the item is available (e.g.
|
|
|
|
|
* for optimisation)
|
|
|
|
|
* @return bool
|
2011-04-30 14:08:12 +00:00
|
|
|
*/
|
|
|
|
|
public function isItemLoaded( $item, $all = 'all' ) {
|
|
|
|
|
return ( $this->mLoadedItems === true && $all === 'all' ) ||
|
|
|
|
|
( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set that an item has been loaded
|
|
|
|
|
*
|
2020-09-27 02:08:38 +00:00
|
|
|
* @internal Only public for use in UserFactory
|
|
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @param string $item
|
2011-04-30 14:08:12 +00:00
|
|
|
*/
|
2020-09-27 02:08:38 +00:00
|
|
|
public function setItemLoaded( $item ) {
|
2011-04-30 14:08:12 +00:00
|
|
|
if ( is_array( $this->mLoadedItems ) ) {
|
|
|
|
|
$this->mLoadedItems[$item] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2016-02-01 20:44:03 +00:00
|
|
|
* Load user data from the session.
|
2015-03-24 00:39:48 +00:00
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if the user is logged in, false otherwise.
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2007-04-25 20:00:18 +00:00
|
|
|
private function loadFromSession() {
|
2016-02-01 20:44:03 +00:00
|
|
|
// MediaWiki\Session\Session already did the necessary authentication of the user
|
|
|
|
|
// returned here, so just use it if applicable.
|
|
|
|
|
$session = $this->getRequest()->getSession();
|
|
|
|
|
$user = $session->getUser();
|
2020-12-17 23:10:11 +00:00
|
|
|
if ( $user->isRegistered() ) {
|
2016-02-01 20:44:03 +00:00
|
|
|
$this->loadFromUserObject( $user );
|
2019-06-26 06:00:07 +00:00
|
|
|
|
2016-02-01 20:44:03 +00:00
|
|
|
// Other code expects these to be set in the session, so set them.
|
|
|
|
|
$session->set( 'wsUserID', $this->getId() );
|
|
|
|
|
$session->set( 'wsUserName', $this->getName() );
|
|
|
|
|
$session->set( 'wsToken', $this->getToken() );
|
2018-05-17 05:55:02 +00:00
|
|
|
|
2016-02-01 17:28:29 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2018-05-17 05:55:02 +00:00
|
|
|
|
2016-02-01 20:44:03 +00:00
|
|
|
return false;
|
2006-10-14 06:58:19 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2019-10-24 03:14:31 +00:00
|
|
|
* Load user data from the database.
|
2011-04-30 14:08:12 +00:00
|
|
|
* $this->mId must be set, this is how the user is identified.
|
2008-04-14 07:45:50 +00:00
|
|
|
*
|
2017-08-20 11:20:59 +00:00
|
|
|
* @param int $flags User::READ_* constant bitfield
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if the user exists, false if the user is anonymous
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2015-03-24 00:39:48 +00:00
|
|
|
public function loadFromDatabase( $flags = self::READ_LATEST ) {
|
2013-06-03 22:02:10 +00:00
|
|
|
// Paranoia
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->mId = intval( $this->mId );
|
|
|
|
|
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( !$this->mId ) {
|
User: Simplify process cache by using WANObjectCache::getWithSetCallback
Follows-up 7d67b4d919, 9c733318.
* Convert loadFromId() to use getWithSetCallback() and centralise
cache access logic there instead of spread between loadFromCache()
and saveToCache().
* Remove process cache from User class (added in 9c733318).
Instead, tell WANObjectCache to process-cache the key for 30 seconds.
* No need to deal with process cache in purge() because load uses slaves by
default and may be lagged. Reads that require READ_LATEST already bypass
the cache.
* Remove saveToCache() and move logic to loadFromCache().
It was technically a public method, but marked private and no longer used
in any extensions.
* Remove redundant isAnon() check in loadFromCache().
This is already done by loadFromId() and loadFromDatabase().
* Remove hasOrMadeRecentMasterChanges() check. It was used to add READ_LATEST
to the flags. However, this check only occurred if either READ_LATEST was
already set, or after consulting cache. Which means in general, it never
does anything. If we want to keep this, we should probably move it higher up.
* Let WANObjectCache handle cache version. That way, there is no longer separate
logic for "populate cache" and "cache lookup failed". Instead, there is
just "get data" that tries cache first.
I've considered moving the version into the cache key (like we do elsewhere)
but that would be problematic here since User cache must be purgeable
cross-wiki and other wikis may run a different version (either in general,
or even just during a deployment). As such, the key must remain unchanged when
the version changes so that purges from newer wikis affect what older wikis see
and vice versa.
Change-Id: Icfbc54dfd0ea594dd52fc1cfd403a7f66f1dd0b0
2016-02-27 19:35:21 +00:00
|
|
|
// Anonymous users are not in the database
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->loadDefaults();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-25 06:19:06 +00:00
|
|
|
list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
|
|
|
|
|
$db = wfGetDB( $index );
|
2015-03-24 00:39:48 +00:00
|
|
|
|
2017-10-06 17:03:55 +00:00
|
|
|
$userQuery = self::getQueryInfo();
|
2015-03-24 00:39:48 +00:00
|
|
|
$s = $db->selectRow(
|
2017-10-06 17:03:55 +00:00
|
|
|
$userQuery['tables'],
|
|
|
|
|
$userQuery['fields'],
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 'user_id' => $this->mId ],
|
2014-06-24 00:40:08 +00:00
|
|
|
__METHOD__,
|
2017-10-06 17:03:55 +00:00
|
|
|
$options,
|
|
|
|
|
$userQuery['joins']
|
2014-05-11 15:34:14 +00:00
|
|
|
);
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2015-03-25 00:30:26 +00:00
|
|
|
$this->queryFlagsUsed = $flags;
|
2021-05-01 01:28:36 +00:00
|
|
|
|
|
|
|
|
// hook is hard deprecated since 1.37
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserLoadFromDatabase( $this, $s );
|
2009-01-16 23:34:38 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
if ( $s !== false ) {
|
2013-06-03 22:02:10 +00:00
|
|
|
// Initialise user table data
|
2008-04-15 09:04:45 +00:00
|
|
|
$this->loadFromRow( $s );
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
// Invalid user_id
|
|
|
|
|
$this->mId = 0;
|
2020-09-24 19:50:32 +00:00
|
|
|
$this->loadDefaults( 'Unknown user' );
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
return false;
|
2008-04-15 09:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Initialize this object from a row from the user table.
|
|
|
|
|
*
|
2013-12-19 18:25:33 +00:00
|
|
|
* @param stdClass $row Row from the user table to load.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param array|null $data Further user data to load into the object
|
2012-09-11 23:45:35 +00:00
|
|
|
*
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
* user_groups Array of arrays or stdClass result rows out of the user_groups
|
|
|
|
|
* table. Previously you were supposed to pass an array of strings
|
|
|
|
|
* here, but we also need expiry info nowadays, so an array of
|
|
|
|
|
* strings is ignored.
|
2008-04-15 09:04:45 +00:00
|
|
|
*/
|
2015-03-26 05:38:34 +00:00
|
|
|
protected function loadFromRow( $row, $data = null ) {
|
2017-09-12 17:12:29 +00:00
|
|
|
if ( !is_object( $row ) ) {
|
|
|
|
|
throw new InvalidArgumentException( '$row must be an object' );
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-30 14:08:12 +00:00
|
|
|
$all = true;
|
|
|
|
|
|
2019-07-23 17:40:52 +00:00
|
|
|
if ( isset( $row->actor_id ) ) {
|
|
|
|
|
$this->mActorId = (int)$row->actor_id;
|
|
|
|
|
if ( $this->mActorId !== 0 ) {
|
|
|
|
|
$this->mFrom = 'actor';
|
2017-09-12 17:12:29 +00:00
|
|
|
}
|
2019-07-23 17:40:52 +00:00
|
|
|
$this->setItemLoaded( 'actor' );
|
|
|
|
|
} else {
|
|
|
|
|
$all = false;
|
2017-09-12 17:12:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( isset( $row->user_name ) && $row->user_name !== '' ) {
|
2011-04-30 14:08:12 +00:00
|
|
|
$this->mName = $row->user_name;
|
|
|
|
|
$this->mFrom = 'name';
|
|
|
|
|
$this->setItemLoaded( 'name' );
|
|
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-04 14:04:33 +00:00
|
|
|
if ( isset( $row->user_real_name ) ) {
|
2011-04-30 14:08:12 +00:00
|
|
|
$this->mRealName = $row->user_real_name;
|
|
|
|
|
$this->setItemLoaded( 'realname' );
|
|
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
2008-04-15 09:04:45 +00:00
|
|
|
|
|
|
|
|
if ( isset( $row->user_id ) ) {
|
2009-02-04 09:10:32 +00:00
|
|
|
$this->mId = intval( $row->user_id );
|
2017-09-12 17:12:29 +00:00
|
|
|
if ( $this->mId !== 0 ) {
|
|
|
|
|
$this->mFrom = 'id';
|
|
|
|
|
}
|
2011-04-30 14:08:12 +00:00
|
|
|
$this->setItemLoaded( 'id' );
|
|
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-07 15:39:01 +00:00
|
|
|
if ( isset( $row->user_editcount ) ) {
|
2021-11-19 23:19:42 +00:00
|
|
|
// Don't try to set edit count for anonymous users
|
2021-06-15 08:08:33 +00:00
|
|
|
// We check the id here and not in UserEditTracker because calling
|
|
|
|
|
// User::getId() can trigger some other loading. This will result in
|
|
|
|
|
// discarding the user_editcount field for rows if the id wasn't set.
|
|
|
|
|
if ( $this->mId !== null && $this->mId !== 0 ) {
|
|
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getUserEditTracker()
|
|
|
|
|
->setCachedUserEditCount( $this, (int)$row->user_editcount );
|
|
|
|
|
}
|
2011-09-07 15:39:01 +00:00
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 17:49:06 +00:00
|
|
|
if ( isset( $row->user_touched ) ) {
|
|
|
|
|
$this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
|
|
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-28 18:46:22 +00:00
|
|
|
if ( isset( $row->user_token ) ) {
|
|
|
|
|
// The definition for the column is binary(32), so trim the NULs
|
|
|
|
|
// that appends. The previous definition was char(32), so trim
|
|
|
|
|
// spaces too.
|
|
|
|
|
$this->mToken = rtrim( $row->user_token, " \0" );
|
|
|
|
|
if ( $this->mToken === '' ) {
|
2012-03-20 05:17:40 +00:00
|
|
|
$this->mToken = null;
|
|
|
|
|
}
|
2016-01-28 18:46:22 +00:00
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( isset( $row->user_email ) ) {
|
|
|
|
|
$this->mEmail = $row->user_email;
|
2011-04-30 14:08:12 +00:00
|
|
|
$this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
|
|
|
|
|
$this->mEmailToken = $row->user_email_token;
|
|
|
|
|
$this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
|
|
|
|
|
$this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
|
|
|
|
|
} else {
|
|
|
|
|
$all = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $all ) {
|
|
|
|
|
$this->mLoadedItems = true;
|
2008-04-15 09:04:45 +00:00
|
|
|
}
|
2012-09-11 23:45:35 +00:00
|
|
|
|
|
|
|
|
if ( is_array( $data ) ) {
|
2019-10-24 03:14:31 +00:00
|
|
|
|
2012-11-09 21:23:14 +00:00
|
|
|
if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) {
|
2019-10-24 03:14:31 +00:00
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
2020-06-09 19:21:10 +00:00
|
|
|
->loadGroupMembershipsFromArray(
|
|
|
|
|
$this,
|
|
|
|
|
$data['user_groups'],
|
|
|
|
|
$this->queryFlagsUsed
|
|
|
|
|
);
|
2012-09-11 23:45:35 +00:00
|
|
|
}
|
|
|
|
|
}
|
2008-04-15 09:04:45 +00:00
|
|
|
}
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2011-05-05 05:29:50 +00:00
|
|
|
/**
|
2011-05-28 14:52:55 +00:00
|
|
|
* Load the data for this user object from another user object.
|
|
|
|
|
*
|
2014-04-23 09:41:35 +00:00
|
|
|
* @param User $user
|
2011-05-05 05:29:50 +00:00
|
|
|
*/
|
|
|
|
|
protected function loadFromUserObject( $user ) {
|
|
|
|
|
$user->load();
|
|
|
|
|
foreach ( self::$mCacheVars as $var ) {
|
|
|
|
|
$this->$var = $user->$var;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-19 12:51:04 +00:00
|
|
|
/**
|
|
|
|
|
* Builds update conditions. Additional conditions may be added to $conditions to
|
|
|
|
|
* protected against race conditions using a compare-and-set (CAS) mechanism
|
|
|
|
|
* based on comparing $this->mTouched with the user_touched field.
|
|
|
|
|
*
|
2019-04-08 03:46:22 +00:00
|
|
|
* @param IDatabase $db
|
2016-09-26 22:40:07 +00:00
|
|
|
* @param array $conditions WHERE conditions for use with Database::update
|
|
|
|
|
* @return array WHERE conditions for use with Database::update
|
2016-05-19 12:51:04 +00:00
|
|
|
*/
|
2019-04-06 23:29:36 +00:00
|
|
|
protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
|
2016-05-19 12:51:04 +00:00
|
|
|
if ( $this->mTouched ) {
|
|
|
|
|
// CAS check: only update if the row wasn't changed sicne it was loaded.
|
|
|
|
|
$conditions['user_touched'] = $db->timestamp( $this->mTouched );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $conditions;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-07 20:50:00 +00:00
|
|
|
/**
|
|
|
|
|
* Bump user_touched if it didn't change since this object was loaded
|
|
|
|
|
*
|
|
|
|
|
* On success, the mTouched field is updated.
|
|
|
|
|
* The user serialization cache is always cleared.
|
|
|
|
|
*
|
2020-06-05 16:33:08 +00:00
|
|
|
* @internal
|
2015-04-07 20:50:00 +00:00
|
|
|
* @return bool Whether user_touched was actually updated
|
|
|
|
|
* @since 1.26
|
|
|
|
|
*/
|
2020-06-05 16:33:08 +00:00
|
|
|
public function checkAndSetTouched() {
|
2015-04-07 20:50:00 +00:00
|
|
|
$this->load();
|
|
|
|
|
|
|
|
|
|
if ( !$this->mId ) {
|
|
|
|
|
return false; // anon
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get a new user_touched that is higher than the old one
|
|
|
|
|
$newTouched = $this->newTouchedTimestamp();
|
|
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$dbw = wfGetDB( DB_PRIMARY );
|
2015-04-07 20:50:00 +00:00
|
|
|
$dbw->update( 'user',
|
2016-02-17 09:09:32 +00:00
|
|
|
[ 'user_touched' => $dbw->timestamp( $newTouched ) ],
|
2016-05-19 12:51:04 +00:00
|
|
|
$this->makeUpdateConditions( $dbw, [
|
2015-04-07 20:50:00 +00:00
|
|
|
'user_id' => $this->mId,
|
2016-05-19 12:51:04 +00:00
|
|
|
] ),
|
2015-04-07 20:50:00 +00:00
|
|
|
__METHOD__
|
|
|
|
|
);
|
|
|
|
|
$success = ( $dbw->affectedRows() > 0 );
|
|
|
|
|
|
|
|
|
|
if ( $success ) {
|
|
|
|
|
$this->mTouched = $newTouched;
|
2019-03-19 03:31:54 +00:00
|
|
|
$this->clearSharedCache( 'changed' );
|
2015-09-28 21:07:01 +00:00
|
|
|
} else {
|
|
|
|
|
// Clears on failure too since that is desired if the cache is stale
|
|
|
|
|
$this->clearSharedCache( 'refresh' );
|
2015-04-07 20:50:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $success;
|
|
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2012-10-19 03:02:39 +00:00
|
|
|
* Clear various cached data stored in this object. The cache of the user table
|
|
|
|
|
* data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
|
|
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @param bool|string $reloadFrom Reload user and user_groups table data from a
|
2017-09-12 17:12:29 +00:00
|
|
|
* given source. May be "name", "id", "actor", "defaults", "session", or false for no reload.
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function clearInstanceCache( $reloadFrom = false ) {
|
2019-04-09 06:58:04 +00:00
|
|
|
global $wgFullyInitialised;
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->mDatePreference = null;
|
|
|
|
|
$this->mBlockedby = -1; # Unset
|
|
|
|
|
$this->mHash = false;
|
2021-02-23 01:24:29 +00:00
|
|
|
$this->mThisAsAuthority = null;
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2019-04-09 06:58:04 +00:00
|
|
|
if ( $wgFullyInitialised && $this->mFrom ) {
|
2020-05-26 03:33:28 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
|
$services->getPermissionManager()->invalidateUsersRightsCache( $this );
|
|
|
|
|
$services->getUserOptionsManager()->clearUserOptionsCache( $this );
|
|
|
|
|
$services->getTalkPageNotificationManager()->clearInstanceCache( $this );
|
2019-10-24 03:14:31 +00:00
|
|
|
$services->getUserGroupManager()->clearCache( $this );
|
2020-05-26 03:33:28 +00:00
|
|
|
$services->getUserEditTracker()->clearUserEditCache( $this );
|
2019-04-09 06:58:04 +00:00
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
if ( $reloadFrom ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
$this->mLoadedItems = [];
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->mFrom = $reloadFrom;
|
|
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2004-09-04 13:06:25 +00:00
|
|
|
* Get blocking information
|
2019-04-05 19:13:17 +00:00
|
|
|
*
|
|
|
|
|
* TODO: Move this into the BlockManager, along with block-related properties.
|
|
|
|
|
*
|
2019-03-18 22:17:50 +00:00
|
|
|
* @param bool $fromReplica Whether to check the replica DB first.
|
2016-09-05 20:21:26 +00:00
|
|
|
* To improve performance, non-critical checks are done against replica DBs.
|
2021-09-01 21:04:40 +00:00
|
|
|
* Check when actually saving should be done against primary DB.
|
2021-01-08 21:21:38 +00:00
|
|
|
* @param bool $disableIpBlockExemptChecking This is used internally to prevent
|
|
|
|
|
* a infinite recursion with autopromote. See T270145.
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2021-01-08 21:21:38 +00:00
|
|
|
private function getBlockedStatus( $fromReplica = true, $disableIpBlockExemptChecking = false ) {
|
2018-06-30 09:43:00 +00:00
|
|
|
if ( $this->mBlockedby != -1 ) {
|
2005-08-23 16:50:39 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2004-08-14 22:30:10 +00:00
|
|
|
|
2020-06-26 00:56:45 +00:00
|
|
|
wfDebug( __METHOD__ . ": checking blocked status for " . $this->getName() );
|
2005-08-21 06:07:48 +00:00
|
|
|
|
2008-05-14 23:42:15 +00:00
|
|
|
// Initialize data...
|
|
|
|
|
// Otherwise something ends up stomping on $this->mBlockedby when
|
|
|
|
|
// things get lazy-loaded later, causing false positive block hits
|
|
|
|
|
// due to -1 !== 0. Probably session-related... Nothing should be
|
|
|
|
|
// overwriting mBlockedby, surely?
|
|
|
|
|
$this->load();
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2019-08-23 16:11:45 +00:00
|
|
|
// TODO: Block checking shouldn't really be done from the User object. Block
|
|
|
|
|
// checking can involve checking for IP blocks, cookie blocks, and/or XFF blocks,
|
|
|
|
|
// which need more knowledge of the request context than the User should have.
|
|
|
|
|
// Since we do currently check blocks from the User, we have to do the following
|
|
|
|
|
// here:
|
|
|
|
|
// - Check if this is the user associated with the main request
|
|
|
|
|
// - If so, pass the relevant request information to the block manager
|
|
|
|
|
$request = null;
|
2021-01-07 20:57:19 +00:00
|
|
|
if ( $this->isGlobalSessionUser() ) {
|
2019-08-23 16:11:45 +00:00
|
|
|
// This is the global user, so we need to pass the request
|
|
|
|
|
$request = $this->getRequest();
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-05 19:13:17 +00:00
|
|
|
$block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
|
|
|
|
|
$this,
|
2019-08-23 16:11:45 +00:00
|
|
|
$request,
|
2021-01-08 21:21:38 +00:00
|
|
|
$fromReplica,
|
|
|
|
|
$disableIpBlockExemptChecking
|
2019-04-05 19:13:17 +00:00
|
|
|
);
|
2016-11-23 19:51:30 +00:00
|
|
|
|
2019-03-19 18:56:10 +00:00
|
|
|
if ( $block ) {
|
2012-03-18 22:19:00 +00:00
|
|
|
$this->mBlock = $block;
|
|
|
|
|
$this->mBlockedby = $block->getByName();
|
2019-03-22 15:16:40 +00:00
|
|
|
$this->mBlockreason = $block->getReason();
|
|
|
|
|
$this->mHideName = $block->getHideName();
|
2012-03-18 22:19:00 +00:00
|
|
|
} else {
|
2018-03-22 16:52:59 +00:00
|
|
|
$this->mBlock = null;
|
2012-03-18 22:19:00 +00:00
|
|
|
$this->mBlockedby = '';
|
2018-03-22 16:52:59 +00:00
|
|
|
$this->mBlockreason = '';
|
2022-03-08 23:35:48 +00:00
|
|
|
$this->mHideName = false;
|
2005-03-28 15:19:24 +00:00
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2007-03-06 04:29:37 +00:00
|
|
|
/**
|
|
|
|
|
* Is this user subject to rate limiting?
|
|
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if rate limited
|
2007-03-06 04:29:37 +00:00
|
|
|
*/
|
|
|
|
|
public function isPingLimitable() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$rateLimitsExcludedIPs = MediaWikiServices::getInstance()->getMainConfig()->get( 'RateLimitsExcludedIPs' );
|
|
|
|
|
if ( IPUtils::isInRanges( $this->getRequest()->getIP(), $rateLimitsExcludedIPs ) ) {
|
2009-10-19 19:11:56 +00:00
|
|
|
// No other good way currently to disable rate limits
|
|
|
|
|
// for specific IPs. :P
|
|
|
|
|
// But this is a crappy hack and should die.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-02-03 20:05:24 +00:00
|
|
|
return !$this->isAllowed( 'noratelimit' );
|
2007-03-06 04:29:37 +00:00
|
|
|
}
|
|
|
|
|
|
2005-05-27 11:03:37 +00:00
|
|
|
/**
|
|
|
|
|
* Primitive rate limits: enforce maximum actions per time period
|
|
|
|
|
* to put a brake on flooding.
|
|
|
|
|
*
|
2014-05-19 10:45:11 +00:00
|
|
|
* The method generates both a generic profiling point and a per action one
|
2020-05-04 15:21:03 +00:00
|
|
|
* (suffix being "-$action").
|
2014-05-19 10:45:11 +00:00
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @note When using a shared cache like memcached, IP-address
|
2005-05-27 11:03:37 +00:00
|
|
|
* last-hit counters will be shared across wikis.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $action Action to enforce; 'edit' if unspecified
|
2014-04-23 09:41:35 +00:00
|
|
|
* @param int $incrBy Positive amount to increment counter by [defaults to 1]
|
2020-08-28 19:05:36 +00:00
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if a rate limiter was tripped
|
2020-08-28 19:05:36 +00:00
|
|
|
* @throws MWException
|
2005-05-27 11:03:37 +00:00
|
|
|
*/
|
2013-09-20 22:26:08 +00:00
|
|
|
public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
|
2021-03-15 15:25:33 +00:00
|
|
|
$logger = LoggerFactory::getInstance( 'ratelimit' );
|
2020-08-26 11:18:46 +00:00
|
|
|
|
2013-06-03 22:02:10 +00:00
|
|
|
// Call the 'PingLimiter' hook
|
2006-12-22 20:21:14 +00:00
|
|
|
$result = false;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
if ( !$this->getHookRunner()->onPingLimiter( $this, $action, $result, $incrBy ) ) {
|
2006-12-22 20:21:14 +00:00
|
|
|
return $result;
|
|
|
|
|
}
|
2007-03-07 18:06:14 +00:00
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
$rateLimits = MediaWikiServices::getInstance()->getMainConfig()->get( 'RateLimits' );
|
|
|
|
|
if ( !isset( $rateLimits[$action] ) ) {
|
2005-05-27 11:03:37 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2007-03-07 18:06:14 +00:00
|
|
|
|
2016-10-04 04:46:30 +00:00
|
|
|
$limits = array_merge(
|
|
|
|
|
[ '&can-bypass' => true ],
|
2022-01-06 18:44:56 +00:00
|
|
|
$rateLimits[$action]
|
2016-10-04 04:46:30 +00:00
|
|
|
);
|
|
|
|
|
|
2013-06-03 22:02:10 +00:00
|
|
|
// Some groups shouldn't trigger the ping limiter, ever
|
2016-10-04 04:46:30 +00:00
|
|
|
if ( $limits['&can-bypass'] && !$this->isPingLimitable() ) {
|
2007-03-06 04:29:37 +00:00
|
|
|
return false;
|
2013-04-20 22:49:30 +00:00
|
|
|
}
|
2007-03-07 18:06:14 +00:00
|
|
|
|
2020-08-26 11:18:46 +00:00
|
|
|
$logger->debug( __METHOD__ . ": limiting $action rate for {$this->getName()}" );
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$keys = [];
|
2005-05-27 11:03:37 +00:00
|
|
|
$id = $this->getId();
|
2016-01-25 23:52:56 +00:00
|
|
|
$isNewbie = $this->isNewbie();
|
2017-05-25 07:48:09 +00:00
|
|
|
$cache = ObjectCache::getLocalClusterInstance();
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2016-01-25 23:52:56 +00:00
|
|
|
if ( $id == 0 ) {
|
2020-06-07 03:00:18 +00:00
|
|
|
// "shared anon" limit, for all anons combined
|
2016-01-25 23:52:56 +00:00
|
|
|
if ( isset( $limits['anon'] ) ) {
|
2017-05-25 07:48:09 +00:00
|
|
|
$keys[$cache->makeKey( 'limiter', $action, 'anon' )] = $limits['anon'];
|
2016-01-25 23:52:56 +00:00
|
|
|
}
|
2020-08-28 19:05:36 +00:00
|
|
|
} else {
|
|
|
|
|
// "global per name" limit, across sites
|
|
|
|
|
if ( isset( $limits['user-global'] ) ) {
|
2021-06-25 15:36:19 +00:00
|
|
|
$lookup = MediaWikiServices::getInstance()
|
|
|
|
|
->getCentralIdLookupFactory()
|
|
|
|
|
->getNonLocalLookup();
|
2020-08-28 19:05:36 +00:00
|
|
|
|
|
|
|
|
$centralId = $lookup
|
|
|
|
|
? $lookup->centralIdFromLocalUser( $this, CentralIdLookup::AUDIENCE_RAW )
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
if ( $centralId ) {
|
|
|
|
|
// We don't have proper realms, use provider ID.
|
|
|
|
|
$realm = $lookup->getProviderId();
|
|
|
|
|
|
|
|
|
|
$globalKey = $cache->makeGlobalKey( 'limiter', $action, 'user-global',
|
|
|
|
|
$realm, $centralId );
|
|
|
|
|
} else {
|
|
|
|
|
// Fall back to a local key for a local ID
|
|
|
|
|
$globalKey = $cache->makeKey( 'limiter', $action, 'user-global',
|
|
|
|
|
'local', $id );
|
|
|
|
|
}
|
|
|
|
|
$keys[$globalKey] = $limits['user-global'];
|
|
|
|
|
}
|
2016-01-25 23:52:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $isNewbie ) {
|
2020-06-07 03:00:18 +00:00
|
|
|
// "per ip" limit for anons and newbie users
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( isset( $limits['ip'] ) ) {
|
2013-05-16 06:19:13 +00:00
|
|
|
$ip = $this->getRequest()->getIP();
|
2020-06-07 03:00:18 +00:00
|
|
|
$keys[$cache->makeGlobalKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
|
2005-05-27 11:03:37 +00:00
|
|
|
}
|
2020-06-07 03:00:18 +00:00
|
|
|
// "per subnet" limit for anons and newbie users
|
2013-05-16 06:19:13 +00:00
|
|
|
if ( isset( $limits['subnet'] ) ) {
|
|
|
|
|
$ip = $this->getRequest()->getIP();
|
2019-06-25 18:53:15 +00:00
|
|
|
$subnet = IPUtils::getSubnet( $ip );
|
2013-05-16 06:19:13 +00:00
|
|
|
if ( $subnet !== false ) {
|
2020-06-07 03:00:18 +00:00
|
|
|
$keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
|
2013-05-16 06:19:13 +00:00
|
|
|
}
|
2005-05-27 11:03:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2016-01-25 23:52:56 +00:00
|
|
|
|
2020-06-07 03:02:49 +00:00
|
|
|
// determine the "per user account" limit
|
|
|
|
|
$userLimit = false;
|
|
|
|
|
if ( $id !== 0 && isset( $limits['user'] ) ) {
|
|
|
|
|
// default limit for logged-in users
|
|
|
|
|
$userLimit = $limits['user'];
|
2008-05-21 03:13:24 +00:00
|
|
|
}
|
2020-06-07 03:00:18 +00:00
|
|
|
// limits for newbie logged-in users (overrides all the normal user limits)
|
2018-03-13 18:43:30 +00:00
|
|
|
if ( $id !== 0 && $isNewbie && isset( $limits['newbie'] ) ) {
|
|
|
|
|
$userLimit = $limits['newbie'];
|
2020-06-07 03:02:49 +00:00
|
|
|
} else {
|
|
|
|
|
// Check for group-specific limits
|
|
|
|
|
// If more than one group applies, use the highest allowance (if higher than the default)
|
2021-07-01 10:32:24 +00:00
|
|
|
$userGroups = MediaWikiServices::getInstance()->getUserGroupManager()->getUserGroups( $this );
|
|
|
|
|
foreach ( $userGroups as $group ) {
|
2020-06-07 03:02:49 +00:00
|
|
|
if ( isset( $limits[$group] ) ) {
|
|
|
|
|
if ( $userLimit === false
|
|
|
|
|
|| $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
|
|
|
|
|
) {
|
|
|
|
|
$userLimit = $limits[$group];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-13 18:43:30 +00:00
|
|
|
}
|
|
|
|
|
|
2008-05-21 03:13:24 +00:00
|
|
|
// Set the user limit key
|
|
|
|
|
if ( $userLimit !== false ) {
|
2019-04-07 07:11:39 +00:00
|
|
|
// phan is confused because &can-bypass's value is a bool, so it assumes
|
|
|
|
|
// that $userLimit is also a bool here.
|
|
|
|
|
// @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
|
2013-04-22 07:52:36 +00:00
|
|
|
list( $max, $period ) = $userLimit;
|
2020-08-26 11:18:46 +00:00
|
|
|
$logger->debug( __METHOD__ . ": effective user limit: $max in {$period}s" );
|
2017-05-25 07:48:09 +00:00
|
|
|
$keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
|
2008-05-21 03:13:24 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2016-01-25 23:52:56 +00:00
|
|
|
// ip-based limits for all ping-limitable users
|
|
|
|
|
if ( isset( $limits['ip-all'] ) ) {
|
|
|
|
|
$ip = $this->getRequest()->getIP();
|
|
|
|
|
// ignore if user limit is more permissive
|
|
|
|
|
if ( $isNewbie || $userLimit === false
|
|
|
|
|
|| $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
|
2020-06-07 03:00:18 +00:00
|
|
|
$keys[$cache->makeGlobalKey( 'limiter', $action, 'ip-all', $ip )] = $limits['ip-all'];
|
2016-01-25 23:52:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// subnet-based limits for all ping-limitable users
|
|
|
|
|
if ( isset( $limits['subnet-all'] ) ) {
|
|
|
|
|
$ip = $this->getRequest()->getIP();
|
2019-06-25 18:53:15 +00:00
|
|
|
$subnet = IPUtils::getSubnet( $ip );
|
2016-01-25 23:52:56 +00:00
|
|
|
if ( $subnet !== false ) {
|
|
|
|
|
// ignore if user limit is more permissive
|
|
|
|
|
if ( $isNewbie || $userLimit === false
|
|
|
|
|
|| $limits['ip-all'][0] / $limits['ip-all'][1]
|
|
|
|
|
> $userLimit[0] / $userLimit[1] ) {
|
2020-06-07 03:00:18 +00:00
|
|
|
$keys[$cache->makeGlobalKey( 'limiter', $action, 'subnet-all', $subnet )] = $limits['subnet-all'];
|
2016-01-25 23:52:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-26 11:18:46 +00:00
|
|
|
// XXX: We may want to use $cache->getCurrentTime() here, but that would make it
|
|
|
|
|
// harder to test for T246991. Also $cache->getCurrentTime() is documented
|
|
|
|
|
// as being for testing only, so it apparently should not be called here.
|
|
|
|
|
$now = MWTimestamp::time();
|
|
|
|
|
$clockFudge = 3; // avoid log spam when a clock is slightly off
|
|
|
|
|
|
2005-05-27 11:03:37 +00:00
|
|
|
$triggered = false;
|
2013-04-20 22:49:30 +00:00
|
|
|
foreach ( $keys as $key => $limit ) {
|
2020-08-26 11:18:46 +00:00
|
|
|
|
|
|
|
|
// Do the update in a merge callback, for atomicity.
|
|
|
|
|
// To use merge(), we need to explicitly track the desired expiry timestamp.
|
|
|
|
|
// This tracking was introduced to investigate T246991. Once it is no longer needed,
|
|
|
|
|
// we could go back to incrWithInit(), though that has more potential for race
|
|
|
|
|
// conditions between the get() and incrWithInit() calls.
|
|
|
|
|
$cache->merge(
|
|
|
|
|
$key,
|
|
|
|
|
function ( $cache, $key, $data, &$expiry )
|
|
|
|
|
use ( $action, $logger, &$triggered, $now, $clockFudge, $limit, $incrBy )
|
|
|
|
|
{
|
|
|
|
|
// phan is confused because &can-bypass's value is a bool, so it assumes
|
|
|
|
|
// that $userLimit is also a bool here.
|
|
|
|
|
// @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
|
|
|
|
|
list( $max, $period ) = $limit;
|
|
|
|
|
|
|
|
|
|
$expiry = $now + (int)$period;
|
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
|
|
// Already pinged?
|
|
|
|
|
if ( $data ) {
|
|
|
|
|
// NOTE: in order to investigate T246991, we write the expiry time
|
|
|
|
|
// into the payload, along with the count.
|
|
|
|
|
$fields = explode( '|', $data );
|
|
|
|
|
$storedCount = (int)( $fields[0] ?? 0 );
|
|
|
|
|
$storedExpiry = (int)( $fields[1] ?? PHP_INT_MAX );
|
|
|
|
|
|
|
|
|
|
// Found a stale entry. This should not happen!
|
|
|
|
|
if ( $storedExpiry < ( $now + $clockFudge ) ) {
|
|
|
|
|
$logger->info(
|
|
|
|
|
'User::pingLimiter: '
|
|
|
|
|
. 'Stale rate limit entry, cache key failed to expire (T246991)',
|
|
|
|
|
[
|
|
|
|
|
'action' => $action,
|
|
|
|
|
'user' => $this->getName(),
|
|
|
|
|
'limit' => $max,
|
|
|
|
|
'period' => $period,
|
|
|
|
|
'count' => $storedCount,
|
|
|
|
|
'key' => $key,
|
|
|
|
|
'expiry' => MWTimestamp::convert( TS_DB, $storedExpiry ),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// NOTE: We'll keep the original expiry when bumping counters,
|
|
|
|
|
// resulting in a kind of fixed-window throttle.
|
|
|
|
|
$expiry = min( $storedExpiry, $now + (int)$period );
|
|
|
|
|
$count = $storedCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Limit exceeded!
|
|
|
|
|
if ( $count >= $max ) {
|
|
|
|
|
if ( !$triggered ) {
|
|
|
|
|
$logger->info(
|
|
|
|
|
'User::pingLimiter: User tripped rate limit',
|
|
|
|
|
[
|
|
|
|
|
'action' => $action,
|
|
|
|
|
'user' => $this->getName(),
|
|
|
|
|
'ip' => $this->getRequest()->getIP(),
|
|
|
|
|
'limit' => $max,
|
|
|
|
|
'period' => $period,
|
|
|
|
|
'count' => $count,
|
|
|
|
|
'key' => $key
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$triggered = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$count += $incrBy;
|
|
|
|
|
$data = "$count|$expiry";
|
|
|
|
|
return $data;
|
2013-09-20 22:26:08 +00:00
|
|
|
}
|
2020-08-26 11:18:46 +00:00
|
|
|
);
|
2005-05-27 11:03:37 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-05-27 11:03:37 +00:00
|
|
|
return $triggered;
|
|
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
|
|
|
|
* Check if user is blocked
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2019-04-23 17:51:54 +00:00
|
|
|
* @deprecated since 1.34, use User::getBlock() or
|
2021-03-10 19:40:33 +00:00
|
|
|
* Authority:getBlock() or Authority:definitlyCan() or
|
|
|
|
|
* Authority:authorizeRead() or Authority:authorizeWrite() or
|
|
|
|
|
* PermissionManager::isBlockedFrom(), as appropriate.
|
2019-04-23 17:51:54 +00:00
|
|
|
*
|
2019-03-18 22:17:50 +00:00
|
|
|
* @param bool $fromReplica Whether to check the replica DB instead of
|
2021-09-01 21:04:40 +00:00
|
|
|
* the primary DB. Hacked from false due to horrible probs on site.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if blocked, false otherwise
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2019-03-18 22:17:50 +00:00
|
|
|
public function isBlocked( $fromReplica = true ) {
|
2021-03-10 19:40:33 +00:00
|
|
|
return $this->getBlock( $fromReplica ) !== null;
|
2011-07-26 19:27:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the block affecting the user, or null if the user is not blocked
|
|
|
|
|
*
|
2021-03-10 19:40:33 +00:00
|
|
|
* @param int|bool $freshness One of the Authority::READ_XXX constants.
|
|
|
|
|
* For backwards compatibility, a boolean is also accepted,
|
|
|
|
|
* with true meaning READ_NORMAL and false meaning
|
|
|
|
|
* READ_LATEST.
|
2021-01-08 21:21:38 +00:00
|
|
|
* @param bool $disableIpBlockExemptChecking This is used internally to prevent
|
|
|
|
|
* a infinite recursion with autopromote. See T270145.
|
2021-03-10 19:40:33 +00:00
|
|
|
*
|
|
|
|
|
* @return ?AbstractBlock
|
2011-07-26 19:27:14 +00:00
|
|
|
*/
|
2021-03-10 19:40:33 +00:00
|
|
|
public function getBlock(
|
|
|
|
|
$freshness = self::READ_NORMAL,
|
|
|
|
|
$disableIpBlockExemptChecking = false
|
|
|
|
|
): ?Block {
|
|
|
|
|
if ( is_bool( $freshness ) ) {
|
|
|
|
|
$fromReplica = $freshness;
|
|
|
|
|
} else {
|
|
|
|
|
$fromReplica = ( $freshness !== self::READ_LATEST );
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-08 21:21:38 +00:00
|
|
|
$this->getBlockedStatus( $fromReplica, $disableIpBlockExemptChecking );
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
return $this->mBlock instanceof AbstractBlock ? $this->mBlock : null;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2005-07-07 21:40:25 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if user is blocked from editing a particular article
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2021-04-08 19:34:40 +00:00
|
|
|
* @param PageIdentity $title Title to check
|
2021-09-01 21:04:40 +00:00
|
|
|
* @param bool $fromReplica Whether to check the replica DB instead of the primary DB
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2019-03-07 20:02:07 +00:00
|
|
|
*
|
|
|
|
|
* @deprecated since 1.33,
|
|
|
|
|
* use MediaWikiServices::getInstance()->getPermissionManager()->isBlockedFrom(..)
|
|
|
|
|
*
|
2005-07-07 21:40:25 +00:00
|
|
|
*/
|
2018-10-31 17:36:48 +00:00
|
|
|
public function isBlockedFrom( $title, $fromReplica = false ) {
|
2019-03-07 20:02:07 +00:00
|
|
|
return MediaWikiServices::getInstance()->getPermissionManager()
|
|
|
|
|
->isBlockedFrom( $this, $title, $fromReplica );
|
2005-07-07 21:40:25 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* If user is blocked, return the name of the user who placed the block
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string Name of blocker
|
2021-09-21 14:05:15 +00:00
|
|
|
* @deprecated since 1.38
|
|
|
|
|
* Hard deprecated since 1.38.
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function blockedBy() {
|
2021-09-21 14:05:15 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.38' );
|
2003-04-14 23:10:40 +00:00
|
|
|
$this->getBlockedStatus();
|
|
|
|
|
return $this->mBlockedby;
|
|
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2019-10-20 00:04:00 +00:00
|
|
|
* If user is blocked, return the specified reason for the block.
|
|
|
|
|
*
|
|
|
|
|
* @deprecated since 1.35 Use AbstractBlock::getReasonComment instead
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string Blocking reason
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function blockedFor() {
|
2003-04-14 23:10:40 +00:00
|
|
|
$this->getBlockedStatus();
|
|
|
|
|
return $this->mBlockreason;
|
|
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2009-02-04 18:23:26 +00:00
|
|
|
/**
|
|
|
|
|
* If user is blocked, return the ID for the block
|
2019-09-19 11:05:36 +00:00
|
|
|
* @return int|false
|
2021-09-21 14:05:15 +00:00
|
|
|
* @deprecated since 1.38
|
|
|
|
|
* Hard deprecated since 1.38.
|
2009-02-04 18:23:26 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getBlockId() {
|
2021-09-21 14:05:15 +00:00
|
|
|
wfDeprecated( __METHOD__, '1.38' );
|
2009-02-04 18:23:26 +00:00
|
|
|
$this->getBlockedStatus();
|
2011-03-21 19:12:41 +00:00
|
|
|
return ( $this->mBlock ? $this->mBlock->getId() : false );
|
2009-02-04 18:23:26 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2008-10-02 01:12:07 +00:00
|
|
|
/**
|
|
|
|
|
* Check if user is blocked on all wikis.
|
|
|
|
|
* Do not use for actual edit permission checks!
|
2013-03-13 07:42:41 +00:00
|
|
|
* This is intended for quick UI checks.
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $ip IP address, uses current client if none given
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if blocked, false otherwise
|
2008-10-02 01:12:07 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isBlockedGlobally( $ip = '' ) {
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
return $this->getGlobalBlock( $ip ) instanceof AbstractBlock;
|
2016-04-13 14:24:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if user is blocked on all wikis.
|
|
|
|
|
* Do not use for actual edit permission checks!
|
|
|
|
|
* This is intended for quick UI checks.
|
|
|
|
|
*
|
|
|
|
|
* @param string $ip IP address, uses current client if none given
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
* @return AbstractBlock|null Block object if blocked, null otherwise
|
2016-04-13 14:24:30 +00:00
|
|
|
* @throws FatalError
|
|
|
|
|
* @throws MWException
|
|
|
|
|
*/
|
|
|
|
|
public function getGlobalBlock( $ip = '' ) {
|
|
|
|
|
if ( $this->mGlobalBlock !== null ) {
|
|
|
|
|
return $this->mGlobalBlock ?: null;
|
2008-10-02 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
// User is already an IP?
|
2019-06-25 18:53:15 +00:00
|
|
|
if ( IPUtils::isIPAddress( $this->getName() ) ) {
|
2008-10-02 01:12:07 +00:00
|
|
|
$ip = $this->getName();
|
2013-04-20 22:49:30 +00:00
|
|
|
} elseif ( !$ip ) {
|
2011-08-18 20:03:30 +00:00
|
|
|
$ip = $this->getRequest()->getIP();
|
2008-10-02 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
$blocked = false;
|
2016-04-13 14:24:30 +00:00
|
|
|
$block = null;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserIsBlockedGlobally( $this, $ip, $blocked, $block );
|
2016-04-13 14:24:30 +00:00
|
|
|
|
|
|
|
|
if ( $blocked && $block === null ) {
|
|
|
|
|
// back-compat: UserIsBlockedGlobally didn't have $block param first
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
$block = new SystemBlock( [
|
2016-12-01 16:51:03 +00:00
|
|
|
'address' => $ip,
|
|
|
|
|
'systemBlock' => 'global-block'
|
|
|
|
|
] );
|
2016-04-13 14:24:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->mGlobalBlock = $blocked ? $block : false;
|
|
|
|
|
return $this->mGlobalBlock ?: null;
|
2008-10-02 01:12:07 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2008-10-02 01:12:07 +00:00
|
|
|
/**
|
|
|
|
|
* Check if user account is locked
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if locked, false otherwise
|
2008-10-02 01:12:07 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isLocked() {
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( $this->mLocked !== null ) {
|
2008-10-02 01:12:07 +00:00
|
|
|
return $this->mLocked;
|
|
|
|
|
}
|
2019-02-11 21:18:13 +00:00
|
|
|
// Reset for hook
|
|
|
|
|
$this->mLocked = false;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserIsLocked( $this, $this->mLocked );
|
2008-10-02 01:12:07 +00:00
|
|
|
return $this->mLocked;
|
|
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2008-10-02 01:12:07 +00:00
|
|
|
/**
|
|
|
|
|
* Check if user account is hidden
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True if hidden, false otherwise
|
2008-10-02 01:12:07 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isHidden() {
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( $this->mHideName !== null ) {
|
2018-08-27 01:45:18 +00:00
|
|
|
return (bool)$this->mHideName;
|
2008-10-02 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
$this->getBlockedStatus();
|
2018-08-27 01:45:18 +00:00
|
|
|
return (bool)$this->mHideName;
|
2008-10-02 01:12:07 +00:00
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the user's ID.
|
2021-02-23 15:40:12 +00:00
|
|
|
* @param string|false $wikiId The wiki ID expected by the caller.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return int The user's ID; 0 if the user is anonymous or nonexistent
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function getId( $wikiId = self::LOCAL ): int {
|
2021-02-23 15:40:12 +00:00
|
|
|
$this->deprecateInvalidCrossWiki( $wikiId, '1.36' );
|
2021-05-10 13:26:24 +00:00
|
|
|
if ( $this->mId === null && $this->mName !== null ) {
|
|
|
|
|
$userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
|
|
|
|
|
if ( $userNameUtils->isIP( $this->mName ) || ExternalUserNames::isExternal( $this->mName ) ) {
|
|
|
|
|
// Special case, we know the user is anonymous
|
|
|
|
|
// Note that "external" users are "local" (they have an actor ID that is relative to
|
|
|
|
|
// the local wiki).
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !$this->isItemLoaded( 'id' ) ) {
|
2007-07-23 19:39:53 +00:00
|
|
|
// Don't load if this was initialized from an ID
|
|
|
|
|
$this->load();
|
|
|
|
|
}
|
2016-03-18 13:23:40 +00:00
|
|
|
|
|
|
|
|
return (int)$this->mId;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Set the user and reload all fields according to a given ID
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param int $v User ID to reload
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function setId( $v ) {
|
2003-04-14 23:10:40 +00:00
|
|
|
$this->mId = $v;
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->clearInstanceCache( 'id' );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the user name, or the IP of an anonymous user
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string User's name or IP address
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function getName(): string {
|
2011-04-30 14:08:12 +00:00
|
|
|
if ( $this->isItemLoaded( 'name', 'only' ) ) {
|
2013-06-03 22:02:10 +00:00
|
|
|
// Special case optimisation
|
2006-10-14 06:58:19 +00:00
|
|
|
return $this->mName;
|
2005-09-05 02:22:20 +00:00
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
$this->load();
|
|
|
|
|
if ( $this->mName === false ) {
|
|
|
|
|
// Clean up IPs
|
2019-06-25 18:53:15 +00:00
|
|
|
$this->mName = IPUtils::sanitizeIP( $this->getRequest()->getIP() );
|
2019-02-07 03:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->mName;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2008-04-14 07:45:50 +00:00
|
|
|
* Set the user name.
|
2006-10-14 06:58:19 +00:00
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* This does not reload fields from the database according to the given
|
2006-10-14 06:58:19 +00:00
|
|
|
* name. Rather, it is used to create a temporary "nonexistent user" for
|
2008-04-14 07:45:50 +00:00
|
|
|
* later addition to the database. It can also be used to set the IP
|
|
|
|
|
* address for an anonymous user to something other than the current
|
2006-10-14 06:58:19 +00:00
|
|
|
* remote IP.
|
|
|
|
|
*
|
2013-03-13 07:42:41 +00:00
|
|
|
* @note User::newFromName() has roughly the same function, when the named user
|
2006-10-14 06:58:19 +00:00
|
|
|
* does not exist.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $str New user name to set
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function setName( $str ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2003-04-14 23:10:40 +00:00
|
|
|
$this->mName = $str;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user's actor ID.
|
|
|
|
|
* @since 1.31
|
2021-02-15 18:58:09 +00:00
|
|
|
* @note This method was removed from the UserIdentity interface in 1.36,
|
2021-02-20 21:51:37 +00:00
|
|
|
* but remains supported in the User class for now.
|
|
|
|
|
* New code should use ActorNormalization::findActorId() or
|
|
|
|
|
* ActorNormalization::acquireActorId() instead.
|
2021-02-15 18:58:09 +00:00
|
|
|
* @param IDatabase|string|false $dbwOrWikiId Deprecated since 1.36.
|
|
|
|
|
* If a database connection is passed, a new actor ID is assigned if needed.
|
|
|
|
|
* ActorNormalization::acquireActorId() should be used for that purpose instead.
|
2017-09-12 17:12:29 +00:00
|
|
|
* @return int The actor's ID, or 0 if no actor ID exists and $dbw was null
|
2021-01-27 04:25:24 +00:00
|
|
|
* @throws PreconditionException if $dbwOrWikiId is a string and does not match the local wiki
|
2017-09-12 17:12:29 +00:00
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function getActorId( $dbwOrWikiId = self::LOCAL ): int {
|
2021-02-15 18:58:09 +00:00
|
|
|
if ( $dbwOrWikiId ) {
|
|
|
|
|
wfDeprecatedMsg( 'Passing a parameter to getActorId() is deprecated', '1.36' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( is_string( $dbwOrWikiId ) ) {
|
2021-01-27 04:25:24 +00:00
|
|
|
$this->assertWiki( $dbwOrWikiId );
|
|
|
|
|
}
|
2021-02-15 22:26:08 +00:00
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
if ( !$this->isItemLoaded( 'actor' ) ) {
|
|
|
|
|
$this->load();
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 04:25:24 +00:00
|
|
|
if ( !$this->mActorId && $dbwOrWikiId instanceof IDatabase ) {
|
2021-01-22 19:51:43 +00:00
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getActorStoreFactory()
|
|
|
|
|
->getActorNormalization( $dbwOrWikiId->getDomainID() )
|
|
|
|
|
->acquireActorId( $this, $dbwOrWikiId );
|
|
|
|
|
// acquireActorId will call setActorId on $this
|
|
|
|
|
Assert::postcondition(
|
|
|
|
|
$this->mActorId !== null,
|
|
|
|
|
"Failed to acquire actor ID for user id {$this->mId} name {$this->mName}"
|
|
|
|
|
);
|
2017-09-12 17:12:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (int)$this->mActorId;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-22 19:51:43 +00:00
|
|
|
/**
|
|
|
|
|
* Sets the actor id.
|
2021-02-15 18:58:09 +00:00
|
|
|
* For use by ActorStore only.
|
|
|
|
|
* Should be removed once callers of getActorId() have been migrated to using ActorNormalization.
|
2021-01-22 19:51:43 +00:00
|
|
|
*
|
|
|
|
|
* @internal
|
|
|
|
|
* @deprecated since 1.36
|
|
|
|
|
* @param int $actorId
|
|
|
|
|
*/
|
|
|
|
|
public function setActorId( int $actorId ) {
|
|
|
|
|
$this->mActorId = $actorId;
|
|
|
|
|
$this->setItemLoaded( 'actor' );
|
|
|
|
|
}
|
|
|
|
|
|
2004-12-18 10:21:03 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the user's name escaped by underscores.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string Username escaped by underscores.
|
2004-12-18 10:21:03 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getTitleKey() {
|
2004-12-18 10:21:03 +00:00
|
|
|
return str_replace( ' ', '_', $this->getName() );
|
|
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2006-10-03 22:30:40 +00:00
|
|
|
/**
|
|
|
|
|
* Generate a current or new-future timestamp to be stored in the
|
|
|
|
|
* user_touched field when we update things.
|
2019-03-15 00:05:52 +00:00
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string Timestamp in TS_MW format
|
2006-10-03 22:30:40 +00:00
|
|
|
*/
|
2015-04-06 18:12:41 +00:00
|
|
|
private function newTouchedTimestamp() {
|
2021-10-16 21:47:01 +00:00
|
|
|
$time = (int)ConvertibleTimestamp::now( TS_UNIX );
|
2019-03-15 00:05:52 +00:00
|
|
|
if ( $this->mTouched ) {
|
2021-10-16 21:47:01 +00:00
|
|
|
$time = max( $time, (int)ConvertibleTimestamp::convert( TS_UNIX, $this->mTouched ) + 1 );
|
2015-04-06 18:12:41 +00:00
|
|
|
}
|
|
|
|
|
|
2021-10-25 19:56:47 +00:00
|
|
|
return ConvertibleTimestamp::convert( TS_MW, $time );
|
2006-10-03 22:30:40 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2006-10-03 22:30:40 +00:00
|
|
|
/**
|
2015-09-28 21:07:01 +00:00
|
|
|
* Clear user data from memcached
|
|
|
|
|
*
|
|
|
|
|
* Use after applying updates to the database; caller's
|
2006-10-03 22:30:40 +00:00
|
|
|
* responsibility to update user_touched if appropriate.
|
|
|
|
|
*
|
|
|
|
|
* Called implicitly from invalidateCache() and saveSettings().
|
2015-09-28 21:07:01 +00:00
|
|
|
*
|
2019-03-19 03:31:54 +00:00
|
|
|
* @param string $mode Use 'refresh' to clear now or 'changed' to clear before DB commit
|
2006-10-03 22:30:40 +00:00
|
|
|
*/
|
2019-03-19 03:31:54 +00:00
|
|
|
public function clearSharedCache( $mode = 'refresh' ) {
|
2015-10-27 23:43:40 +00:00
|
|
|
if ( !$this->getId() ) {
|
2015-09-24 22:25:44 +00:00
|
|
|
return;
|
2005-12-07 11:52:34 +00:00
|
|
|
}
|
2015-09-24 22:25:44 +00:00
|
|
|
|
2019-03-19 03:31:54 +00:00
|
|
|
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
|
2019-02-07 03:30:57 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
2015-10-27 23:43:40 +00:00
|
|
|
$key = $this->getCacheKey( $cache );
|
2019-03-19 03:31:54 +00:00
|
|
|
|
2015-09-28 21:07:01 +00:00
|
|
|
if ( $mode === 'refresh' ) {
|
2019-03-19 03:31:54 +00:00
|
|
|
$cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
|
2015-09-28 21:07:01 +00:00
|
|
|
} else {
|
2021-04-29 02:37:11 +00:00
|
|
|
$lb->getConnectionRef( DB_PRIMARY )->onTransactionPreCommitOrIdle(
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () use ( $cache, $key ) {
|
2019-03-19 03:31:54 +00:00
|
|
|
$cache->delete( $key );
|
|
|
|
|
},
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2015-09-28 21:07:01 +00:00
|
|
|
}
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2003-04-16 07:30:52 +00:00
|
|
|
|
2006-10-03 22:30:40 +00:00
|
|
|
/**
|
2015-04-08 02:58:40 +00:00
|
|
|
* Immediately touch the user data cache for this account
|
|
|
|
|
*
|
|
|
|
|
* Calls touch() and removes account data from memcached
|
2006-10-03 22:30:40 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function invalidateCache() {
|
2015-04-08 02:58:40 +00:00
|
|
|
$this->touch();
|
2019-03-19 03:31:54 +00:00
|
|
|
$this->clearSharedCache( 'changed' );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2003-04-16 07:30:52 +00:00
|
|
|
|
2015-03-26 05:19:32 +00:00
|
|
|
/**
|
|
|
|
|
* Update the "touched" timestamp for the user
|
|
|
|
|
*
|
|
|
|
|
* This is useful on various login/logout events when making sure that
|
|
|
|
|
* a browser or proxy that has multiple tenants does not suffer cache
|
|
|
|
|
* pollution where the new user sees the old users content. The value
|
|
|
|
|
* of getTouched() is checked when determining 304 vs 200 responses.
|
|
|
|
|
* Unlike invalidateCache(), this preserves the User object cache and
|
|
|
|
|
* avoids database writes.
|
|
|
|
|
*
|
|
|
|
|
* @since 1.25
|
|
|
|
|
*/
|
|
|
|
|
public function touch() {
|
2015-06-02 00:23:06 +00:00
|
|
|
$id = $this->getId();
|
|
|
|
|
if ( $id ) {
|
2017-05-25 19:17:27 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
|
|
|
|
$key = $cache->makeKey( 'user-quicktouched', 'id', $id );
|
|
|
|
|
$cache->touchCheckKey( $key );
|
2015-06-02 00:23:06 +00:00
|
|
|
$this->mQuickTouched = null;
|
2015-03-26 05:19:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Validate the cache for this account.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $timestamp A timestamp in TS_MW format
|
2011-07-18 20:11:53 +00:00
|
|
|
* @return bool
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function validateCache( $timestamp ) {
|
2015-03-30 16:36:49 +00:00
|
|
|
return ( $timestamp >= $this->getTouched() );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2003-04-16 07:30:52 +00:00
|
|
|
|
2008-08-29 08:40:13 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user touched timestamp
|
2015-05-22 05:46:25 +00:00
|
|
|
*
|
|
|
|
|
* Use this value only to validate caches via inequalities
|
|
|
|
|
* such as in the case of HTTP If-Modified-Since response logic
|
|
|
|
|
*
|
2015-03-26 05:19:32 +00:00
|
|
|
* @return string TS_MW Timestamp
|
2008-08-29 08:40:13 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getTouched() {
|
2008-08-29 08:40:13 +00:00
|
|
|
$this->load();
|
2015-03-26 05:19:32 +00:00
|
|
|
|
|
|
|
|
if ( $this->mId ) {
|
|
|
|
|
if ( $this->mQuickTouched === null ) {
|
2017-05-25 19:17:27 +00:00
|
|
|
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
|
|
|
|
|
$key = $cache->makeKey( 'user-quicktouched', 'id', $this->mId );
|
2015-04-27 23:57:08 +00:00
|
|
|
|
2015-05-22 05:46:25 +00:00
|
|
|
$this->mQuickTouched = wfTimestamp( TS_MW, $cache->getCheckKeyTime( $key ) );
|
2015-03-26 05:19:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return max( $this->mTouched, $this->mQuickTouched );
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-29 08:40:13 +00:00
|
|
|
return $this->mTouched;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-07 20:50:00 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user_touched timestamp field (time of last DB updates)
|
|
|
|
|
* @return string TS_MW Timestamp
|
|
|
|
|
* @since 1.26
|
|
|
|
|
*/
|
2015-05-21 23:24:42 +00:00
|
|
|
public function getDBTouched() {
|
2015-04-07 20:50:00 +00:00
|
|
|
$this->load();
|
|
|
|
|
|
|
|
|
|
return $this->mTouched;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-08 20:08:45 +00:00
|
|
|
/**
|
|
|
|
|
* Changes credentials of the user.
|
|
|
|
|
*
|
|
|
|
|
* This is a convenience wrapper around AuthManager::changeAuthenticationData.
|
|
|
|
|
* Note that this can return a status that isOK() but not isGood() on certain types of failures,
|
|
|
|
|
* e.g. when no provider handled the change.
|
|
|
|
|
*
|
|
|
|
|
* @param array $data A set of authentication data in fieldname => value format. This is the
|
|
|
|
|
* same data you would pass the changeauthenticationdata API - 'username', 'password' etc.
|
|
|
|
|
* @return Status
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
|
|
|
|
public function changeAuthenticationData( array $data ) {
|
2020-03-31 18:51:49 +00:00
|
|
|
$manager = MediaWikiServices::getInstance()->getAuthManager();
|
2016-06-08 20:08:45 +00:00
|
|
|
$reqs = $manager->getAuthenticationRequests( AuthManager::ACTION_CHANGE, $this );
|
|
|
|
|
$reqs = AuthenticationRequest::loadRequestsFromSubmission( $reqs, $data );
|
|
|
|
|
|
|
|
|
|
$status = Status::newGood( 'ignored' );
|
|
|
|
|
foreach ( $reqs as $req ) {
|
|
|
|
|
$status->merge( $manager->allowsAuthenticationDataChange( $req ), true );
|
|
|
|
|
}
|
|
|
|
|
if ( $status->getValue() === 'ignored' ) {
|
|
|
|
|
$status->warning( 'authenticationdatachange-ignored' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $status->isGood() ) {
|
|
|
|
|
foreach ( $reqs as $req ) {
|
|
|
|
|
$manager->changeAuthenticationData( $req );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $status;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user's current token.
|
2014-05-11 15:34:14 +00:00
|
|
|
* @param bool $forceCreation Force the generation of a new token if the
|
|
|
|
|
* user doesn't have one (default=true for backwards compatibility).
|
2016-02-01 20:07:09 +00:00
|
|
|
* @return string|null Token
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2012-03-20 05:17:40 +00:00
|
|
|
public function getToken( $forceCreation = true ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$authenticationTokenVersion = MediaWikiServices::getInstance()
|
|
|
|
|
->getMainConfig()->get( 'AuthenticationTokenVersion' );
|
2016-02-01 20:07:09 +00:00
|
|
|
|
2008-05-29 18:57:06 +00:00
|
|
|
$this->load();
|
2012-03-20 05:17:40 +00:00
|
|
|
if ( !$this->mToken && $forceCreation ) {
|
|
|
|
|
$this->setToken();
|
|
|
|
|
}
|
2016-02-01 20:07:09 +00:00
|
|
|
|
|
|
|
|
if ( !$this->mToken ) {
|
2016-02-01 21:59:27 +00:00
|
|
|
// The user doesn't have a token, return null to indicate that.
|
2016-02-01 20:07:09 +00:00
|
|
|
return null;
|
2019-02-07 03:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $this->mToken === self::INVALID_TOKEN ) {
|
2016-02-01 21:59:27 +00:00
|
|
|
// We return a random value here so existing token checks are very
|
|
|
|
|
// likely to fail.
|
|
|
|
|
return MWCryptRand::generateHex( self::TOKEN_LENGTH );
|
2019-02-07 03:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $authenticationTokenVersion === null ) {
|
2016-02-01 21:59:27 +00:00
|
|
|
// $wgAuthenticationTokenVersion not in use, so return the raw secret
|
2016-02-01 20:07:09 +00:00
|
|
|
return $this->mToken;
|
|
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
// $wgAuthenticationTokenVersion in use, so hmac it.
|
2022-01-06 18:44:56 +00:00
|
|
|
$ret = MWCryptHash::hmac( $authenticationTokenVersion, $this->mToken, false );
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
// The raw hash can be overly long. Shorten it up.
|
|
|
|
|
$len = max( 32, self::TOKEN_LENGTH );
|
|
|
|
|
if ( strlen( $ret ) < $len ) {
|
|
|
|
|
// Should never happen, even md5 is 128 bits
|
|
|
|
|
throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return substr( $ret, -$len );
|
2008-05-29 18:57:06 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
|
|
|
|
* Set the random token (used for persistent authentication)
|
|
|
|
|
* Called from loadDefaults() among other places.
|
2008-08-05 13:42:02 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string|bool $token If specified, set the token to this value
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function setToken( $token = false ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2016-02-01 21:59:27 +00:00
|
|
|
if ( $this->mToken === self::INVALID_TOKEN ) {
|
2021-03-15 15:25:33 +00:00
|
|
|
LoggerFactory::getInstance( 'session' )
|
2016-02-01 21:59:27 +00:00
|
|
|
->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
|
|
|
|
|
} elseif ( !$token ) {
|
2014-07-24 00:26:27 +00:00
|
|
|
$this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
|
2004-09-26 08:25:12 +00:00
|
|
|
} else {
|
|
|
|
|
$this->mToken = $token;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user's e-mail address
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string User's email address
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2021-04-01 19:33:20 +00:00
|
|
|
public function getEmail(): string {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2021-05-12 07:17:12 +00:00
|
|
|
$email = $this->mEmail;
|
|
|
|
|
$this->getHookRunner()->onUserGetEmail( $this, $email );
|
|
|
|
|
// In case a hook handler returns e.g. null
|
|
|
|
|
$this->mEmail = is_string( $email ) ? $email : '';
|
2003-04-14 23:10:40 +00:00
|
|
|
return $this->mEmail;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the timestamp of the user's e-mail authentication
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string TS_MW timestamp
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getEmailAuthenticationTimestamp() {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserGetEmailAuthenticationTimestamp(
|
|
|
|
|
$this, $this->mEmailAuthenticated );
|
2005-04-25 18:38:43 +00:00
|
|
|
return $this->mEmailAuthenticated;
|
2004-12-18 03:47:11 +00:00
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Set the user's e-mail address
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $str New e-mail address
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2021-04-01 19:33:20 +00:00
|
|
|
public function setEmail( string $str ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2021-01-12 21:47:48 +00:00
|
|
|
if ( $str == $this->getEmail() ) {
|
2011-12-11 15:31:17 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
$this->invalidateEmail();
|
2013-05-01 22:17:02 +00:00
|
|
|
$this->mEmail = $str;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserSetEmail( $this, $this->mEmail );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-02 15:57:00 +00:00
|
|
|
/**
|
|
|
|
|
* Set the user's e-mail address and a confirmation mail if needed.
|
|
|
|
|
*
|
2012-04-03 16:44:06 +00:00
|
|
|
* @since 1.20
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $str New e-mail address
|
2012-04-02 15:57:00 +00:00
|
|
|
* @return Status
|
|
|
|
|
*/
|
2021-04-01 19:33:20 +00:00
|
|
|
public function setEmailWithConfirmation( string $str ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$enableEmail = MediaWikiServices::getInstance()->getMainConfig()->get( 'EnableEmail' );
|
|
|
|
|
$emailAuthentication = MediaWikiServices::getInstance()->getMainConfig()->get( 'EmailAuthentication' );
|
|
|
|
|
if ( !$enableEmail ) {
|
2012-04-02 15:57:00 +00:00
|
|
|
return Status::newFatal( 'emaildisabled' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$oldaddr = $this->getEmail();
|
|
|
|
|
if ( $str === $oldaddr ) {
|
|
|
|
|
return Status::newGood( true );
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-10 20:19:41 +00:00
|
|
|
$type = $oldaddr != '' ? 'changed' : 'set';
|
|
|
|
|
$notificationResult = null;
|
|
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $emailAuthentication && $type === 'changed' ) {
|
2016-03-10 20:19:41 +00:00
|
|
|
// Send the user an email notifying the user of the change in registered
|
|
|
|
|
// email address on their previous email address
|
2019-02-07 03:28:29 +00:00
|
|
|
$change = $str != '' ? 'changed' : 'removed';
|
|
|
|
|
$notificationResult = $this->sendMail(
|
|
|
|
|
wfMessage( 'notificationemail_subject_' . $change )->text(),
|
|
|
|
|
wfMessage( 'notificationemail_body_' . $change,
|
|
|
|
|
$this->getRequest()->getIP(),
|
|
|
|
|
$this->getName(),
|
|
|
|
|
$str )->text()
|
|
|
|
|
);
|
2016-03-10 20:19:41 +00:00
|
|
|
}
|
|
|
|
|
|
2012-04-02 15:57:00 +00:00
|
|
|
$this->setEmail( $str );
|
|
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $str !== '' && $emailAuthentication ) {
|
2013-06-03 22:02:10 +00:00
|
|
|
// Send a confirmation request to the new address if needed
|
2012-04-02 15:57:00 +00:00
|
|
|
$result = $this->sendConfirmationMail( $type );
|
2016-03-10 20:19:41 +00:00
|
|
|
|
|
|
|
|
if ( $notificationResult !== null ) {
|
|
|
|
|
$result->merge( $notificationResult );
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-02 15:57:00 +00:00
|
|
|
if ( $result->isGood() ) {
|
2016-03-10 20:19:41 +00:00
|
|
|
// Say to the caller that a confirmation and notification mail has been sent
|
2012-04-05 18:02:59 +00:00
|
|
|
$result->value = 'eauth';
|
2012-04-02 15:57:00 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$result = Status::newGood( true );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user's real name
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string User's real name
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2021-04-01 19:33:20 +00:00
|
|
|
public function getRealName(): string {
|
2011-04-30 14:08:12 +00:00
|
|
|
if ( !$this->isItemLoaded( 'realname' ) ) {
|
|
|
|
|
$this->load();
|
|
|
|
|
}
|
|
|
|
|
|
2004-04-18 02:28:35 +00:00
|
|
|
return $this->mRealName;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Set the user's real name
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $str New real name
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2021-04-01 19:33:20 +00:00
|
|
|
public function setRealName( string $str ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2004-04-18 02:28:35 +00:00
|
|
|
$this->mRealName = $str;
|
|
|
|
|
}
|
|
|
|
|
|
2006-01-08 03:40:48 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the user's current setting for a given option.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $oname The option to check
|
2020-02-05 03:20:05 +00:00
|
|
|
* @param mixed|null $defaultOverride A default value returned if the option does not exist.
|
|
|
|
|
* Default values set via $wgDefaultUserOptions / UserGetDefaultOptions take precedence.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs
|
2019-12-21 12:58:31 +00:00
|
|
|
* @return mixed|null User's current value for the option
|
2020-01-17 06:21:28 +00:00
|
|
|
* @deprecated since 1.35 Use UserOptionsLookup::getOption instead
|
2006-01-08 03:40:48 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
|
2020-01-17 06:21:28 +00:00
|
|
|
if ( $oname === null ) {
|
|
|
|
|
return null; // b/c
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2020-01-17 06:21:28 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserOptionsLookup()
|
|
|
|
|
->getOption( $this, $oname, $defaultOverride, $ignoreHidden );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
/**
|
|
|
|
|
* Get a token stored in the preferences (like the watchlist one),
|
|
|
|
|
* resetting it if it's empty (and saving changes).
|
|
|
|
|
*
|
|
|
|
|
* @param string $oname The option name to retrieve the token from
|
|
|
|
|
* @return string|bool User's current value for the option, or false if this option is disabled.
|
|
|
|
|
* @see resetTokenFromOption()
|
|
|
|
|
* @see getOption()
|
2016-08-02 21:53:25 +00:00
|
|
|
* @deprecated since 1.26 Applications should use the OAuth extension
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
*/
|
|
|
|
|
public function getTokenFromOption( $oname ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$hiddenPrefs = MediaWikiServices::getInstance()->getMainConfig()->get( 'HiddenPrefs' );
|
2015-09-03 00:02:38 +00:00
|
|
|
|
|
|
|
|
$id = $this->getId();
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( !$id || in_array( $oname, $hiddenPrefs ) ) {
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$token = $this->getOption( $oname );
|
|
|
|
|
if ( !$token ) {
|
2015-09-03 00:02:38 +00:00
|
|
|
// Default to a value based on the user token to avoid space
|
|
|
|
|
// wasted on storing tokens for all users. When this option
|
|
|
|
|
// is set manually by the user, only then is it stored.
|
|
|
|
|
$token = hash_hmac( 'sha1', "$oname:$id", $this->getToken() );
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
}
|
2015-09-03 00:02:38 +00:00
|
|
|
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
return $token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset a token stored in the preferences (like the watchlist one).
|
|
|
|
|
* *Does not* save user's preferences (similarly to setOption()).
|
|
|
|
|
*
|
|
|
|
|
* @param string $oname The option name to reset the token in
|
|
|
|
|
* @return string|bool New token value, or false if this option is disabled.
|
|
|
|
|
* @see getTokenFromOption()
|
|
|
|
|
* @see setOption()
|
|
|
|
|
*/
|
|
|
|
|
public function resetTokenFromOption( $oname ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$hiddenPrefs = MediaWikiServices::getInstance()->getMainConfig()->get( 'HiddenPrefs' );
|
|
|
|
|
if ( in_array( $oname, $hiddenPrefs ) ) {
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$token = MWCryptRand::generateHex( 40 );
|
2021-09-10 14:06:51 +00:00
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getUserOptionsManager()
|
|
|
|
|
->setOption( $this, $oname, $token );
|
Refactor watchlist token handling
Do not allow the user to change it directly; instead create a form
where they can reset it. (The token can still be changed via the API.)
The token is autogenerated whenever it is shown or otherwise used.
This really should have never used the preferences; however, trying to
change that now would be lots of work for very little gain, so this
keeps using that mechanism, adding a little abstraction over it.
It's not unconceivable that similar tokens could be used for other
pieces of data, like Echo's notifications; this enables that with one
new hook.
----
Things done here:
* Add getTokenFromOption() and resetTokenFromOption() methods to User,
abstracting out the get-and-generate-if-empty process of handling
tokens. Respect $wgHiddenPrefs (Watchlist didn't do that
previously).
* Create Special:ResetTokens, inspired by Special:Preferences and
Special:ChangeEmail, presenting the token resetting interface
(HTMLForm-based with CSRF protection).
* Create a new hook, SpecialResetTokensTokens, allowing extensions to
register tokens to be shown in the resetting form. Each token needs
information about the preference it corresponds to and a short
description (used for checkbox label).
* Hide the preference on Special:Preferences (use type=api to achieve
this), display a link to aforementioned special page instead. Move
info blurb to its own section at the bottom.
Bug: 21912
Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8
2013-06-14 16:59:59 +00:00
|
|
|
return $token;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user's preferred date format.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string User's preferred date format
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getDatePreference() {
|
2008-08-05 13:42:02 +00:00
|
|
|
// Important migration for old data rows
|
2020-01-09 23:48:34 +00:00
|
|
|
if ( $this->mDatePreference === null ) {
|
2008-08-05 13:42:02 +00:00
|
|
|
global $wgLang;
|
|
|
|
|
$value = $this->getOption( 'date' );
|
|
|
|
|
$map = $wgLang->getDatePreferenceMigrationMap();
|
|
|
|
|
if ( isset( $map[$value] ) ) {
|
|
|
|
|
$value = $map[$value];
|
|
|
|
|
}
|
|
|
|
|
$this->mDatePreference = $value;
|
|
|
|
|
}
|
|
|
|
|
return $this->mDatePreference;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-01 17:25:53 +00:00
|
|
|
/**
|
|
|
|
|
* Determine based on the wiki configuration and the user's options,
|
|
|
|
|
* whether this user must be over HTTPS no matter what.
|
|
|
|
|
*
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function requiresHTTPS() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$forceHTTPS = MediaWikiServices::getInstance()->getMainConfig()->get( 'ForceHTTPS' );
|
|
|
|
|
$secureLogin = MediaWikiServices::getInstance()->getMainConfig()->get( 'SecureLogin' );
|
|
|
|
|
if ( $forceHTTPS ) {
|
Introduce $wgForceHTTPS
Add $wgForceHTTPS. When set to true:
* It makes the HTTP to HTTPS redirect unconditional and suppresses the
forceHTTPS cookie.
* It makes session cookies be secure.
* In the Action API, it triggers the existing deprecation warning and
avoids more expensive user/session checks.
* In login and signup, it suppresses the old hidden form fields for
protocol switching.
* It hides the prefershttps user preference.
Other changes:
* Factor out the HTTPS redirect in MediaWiki::main() into
maybeDoHttpsRedirect() and shouldDoHttpRedirect(). Improve
documentation.
* User::requiresHTTPS() reflects $wgForceHTTPS whereas the Session
concept of "force HTTPS" does not. The documentation of
User::requiresHTTPS() says that it includes configuration, and
retaining this definition was beneficial for some callers. Whereas
Session::shouldForceHTTPS() was used fairly narrowly as the value
of the forceHTTPS cookie, and injecting configuration into it is not
so easy or beneficial, so I left it as it was, except for clarifying
the documentation.
* Deprecate the following hooks: BeforeHttpsRedirect, UserRequiresHTTPS,
CanIPUseHTTPS. No known extension uses them, and they're not compatible
with the long-term goal of ending support for mixed-protocol wikis.
BeforeHttpsRedirect was documented as unstable from its inception.
CanIPUseHTTPS was a WMF config hack now superseded by GFOC's SNI
sniffing.
* For tests which failed with $wgForceHTTPS=true, I mostly split the
tests, testing each configuration value separately.
* Add ArrayUtils::cartesianProduct() as a helper for generating
combinations of boolean options in the session tests.
Bug: T256095
Change-Id: Iefb5ba55af35350dfc7c050f9fb8f4e8a79751cb
2020-06-24 00:56:46 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( !$secureLogin ) {
|
2013-02-01 17:25:53 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2021-08-29 16:51:22 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserOptionsLookup()
|
|
|
|
|
->getBoolOption( $this, 'prefershttps' );
|
2013-02-01 17:25:53 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-09 09:49:10 +00:00
|
|
|
/**
|
|
|
|
|
* Get the list of explicit group memberships this user has.
|
|
|
|
|
* The implicit * and user groups are not included.
|
2017-04-28 21:08:32 +00:00
|
|
|
*
|
2019-10-24 03:14:31 +00:00
|
|
|
* @deprecated since 1.35 Use UserGroupManager::getUserGroups instead.
|
|
|
|
|
*
|
2017-04-28 21:08:32 +00:00
|
|
|
* @return string[] Array of internal group names (sorted since 1.33)
|
2005-06-09 09:49:10 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getGroups() {
|
2019-10-24 03:14:31 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
|
|
|
|
->getUserGroups( $this, $this->queryFlagsUsed );
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the list of explicit group memberships this user has, stored as
|
|
|
|
|
* UserGroupMembership objects. Implicit groups are not included.
|
|
|
|
|
*
|
2019-10-24 03:14:31 +00:00
|
|
|
* @deprecated since 1.35 Use UserGroupManager::getUserGroupMemberships instead
|
|
|
|
|
*
|
2017-12-27 11:40:19 +00:00
|
|
|
* @return UserGroupMembership[] Associative array of (group name => UserGroupMembership object)
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
* @since 1.29
|
|
|
|
|
*/
|
|
|
|
|
public function getGroupMemberships() {
|
2019-10-24 03:14:31 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
|
|
|
|
->getUserGroupMemberships( $this, $this->queryFlagsUsed );
|
2004-10-01 15:57:09 +00:00
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the user's edit count.
|
2014-07-24 17:42:24 +00:00
|
|
|
* @return int|null Null for anonymous users
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getEditCount() {
|
2021-06-15 08:08:33 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserEditTracker()
|
|
|
|
|
->getUserEditCount( $this );
|
2007-02-15 14:27:15 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2005-06-09 09:49:10 +00:00
|
|
|
/**
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
* Add the user to the given group. This takes immediate effect.
|
|
|
|
|
* If the user is already in the group, the expiry time will be updated to the new
|
|
|
|
|
* expiry time. (If $expiry is omitted or null, the membership will be altered to
|
|
|
|
|
* never expire.)
|
|
|
|
|
*
|
2019-10-24 03:14:31 +00:00
|
|
|
* @deprecated since 1.35 Use UserGroupManager::addUserToGroup instead
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $group Name of the group to add
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param string|null $expiry Optional expiry timestamp in any format acceptable to
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
* wfTimestamp(), or null if the group assignment should not expire
|
2014-12-16 22:34:12 +00:00
|
|
|
* @return bool
|
2005-06-09 09:49:10 +00:00
|
|
|
*/
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
public function addGroup( $group, $expiry = null ) {
|
2019-10-24 03:14:31 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
2020-06-13 14:32:59 +00:00
|
|
|
->addUserToGroup( $this, $group, $expiry, true );
|
2005-06-09 09:49:10 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-06-09 09:49:10 +00:00
|
|
|
/**
|
|
|
|
|
* Remove the user from the given group.
|
|
|
|
|
* This takes immediate effect.
|
2019-10-24 03:14:31 +00:00
|
|
|
*
|
|
|
|
|
* @deprecated since 1.35 Use UserGroupManager::removeUserFromGroup instead.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $group Name of the group to remove
|
2014-12-16 22:34:12 +00:00
|
|
|
* @return bool
|
2005-06-09 09:49:10 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function removeGroup( $group ) {
|
2019-10-24 03:14:31 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
|
|
|
|
->removeUserFromGroup( $this, $group );
|
2004-10-01 15:57:09 +00:00
|
|
|
}
|
|
|
|
|
|
2019-04-28 11:07:18 +00:00
|
|
|
/**
|
2020-12-17 23:10:11 +00:00
|
|
|
* Get whether the user is registered.
|
2019-04-28 11:07:18 +00:00
|
|
|
*
|
|
|
|
|
* @return bool True if user is registered on this wiki, i.e., has a user ID. False if user is
|
|
|
|
|
* anonymous or has no local account (which can happen when importing). This is equivalent to
|
|
|
|
|
* getId() != 0 and is provided for code readability.
|
|
|
|
|
* @since 1.34
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function isRegistered(): bool {
|
2019-04-28 11:07:18 +00:00
|
|
|
return $this->getId() != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2005-02-21 12:23:52 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get whether the user is anonymous
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2005-02-21 12:23:52 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isAnon() {
|
2019-04-28 11:07:18 +00:00
|
|
|
return !$this->isRegistered();
|
2005-02-21 12:23:52 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2016-05-08 09:11:14 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool Whether this user is flagged as being a bot role account
|
|
|
|
|
* @since 1.28
|
|
|
|
|
*/
|
|
|
|
|
public function isBot() {
|
2021-07-01 10:32:24 +00:00
|
|
|
$userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager();
|
|
|
|
|
if ( in_array( 'bot', $userGroupManager->getUserGroups( $this ) ) && $this->isAllowed( 'bot' ) ) {
|
2016-05-12 20:02:41 +00:00
|
|
|
return true;
|
2016-05-08 09:11:14 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-12 20:02:41 +00:00
|
|
|
$isBot = false;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserIsBot( $this, $isBot );
|
2016-05-12 20:02:41 +00:00
|
|
|
|
|
|
|
|
return $isBot;
|
2016-05-08 09:11:14 +00:00
|
|
|
}
|
|
|
|
|
|
2019-11-05 22:42:09 +00:00
|
|
|
/**
|
|
|
|
|
* Get whether the user is a system user
|
|
|
|
|
*
|
|
|
|
|
* A user is considered to exist as a non-system user if it can
|
|
|
|
|
* authenticate, or has an email set, or has a non-invalid token.
|
|
|
|
|
*
|
|
|
|
|
* @return bool Whether this user is a system user
|
|
|
|
|
* @since 1.35
|
|
|
|
|
*/
|
|
|
|
|
public function isSystemUser() {
|
|
|
|
|
$this->load();
|
2021-01-12 21:47:48 +00:00
|
|
|
if ( $this->getEmail() || $this->mToken !== self::INVALID_TOKEN ||
|
2020-03-31 18:51:49 +00:00
|
|
|
MediaWikiServices::getInstance()->getAuthManager()->userCanAuthenticate( $this->mName )
|
2019-11-05 22:42:09 +00:00
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
public function isAllowedAny( ...$permissions ): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->isAllowedAny( ...$permissions );
|
2011-03-18 14:48:21 +00:00
|
|
|
}
|
|
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
public function isAllowedAll( ...$permissions ): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->isAllowedAll( ...$permissions );
|
2011-03-18 14:48:21 +00:00
|
|
|
}
|
|
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
public function isAllowed( string $permission ): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->isAllowed( $permission );
|
2004-10-24 19:14:48 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-04-13 17:37:41 +00:00
|
|
|
/**
|
2009-10-04 13:32:48 +00:00
|
|
|
* Check whether to enable recent changes patrol features for this user
|
2014-04-23 09:41:35 +00:00
|
|
|
* @return bool True or false
|
2009-10-04 13:32:48 +00:00
|
|
|
*/
|
2008-04-13 17:37:41 +00:00
|
|
|
public function useRCPatrol() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseRCPatrol' );
|
|
|
|
|
return $useRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
|
2008-04-13 17:37:41 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-04-13 17:37:41 +00:00
|
|
|
/**
|
2009-10-04 13:32:48 +00:00
|
|
|
* Check whether to enable new pages patrol features for this user
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool True or false
|
2009-10-04 13:32:48 +00:00
|
|
|
*/
|
2008-04-13 17:37:41 +00:00
|
|
|
public function useNPPatrol() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseRCPatrol' );
|
|
|
|
|
$useNPPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseNPPatrol' );
|
2013-06-03 22:02:10 +00:00
|
|
|
return (
|
2022-01-06 18:44:56 +00:00
|
|
|
( $useRCPatrol || $useNPPatrol )
|
2013-06-03 22:02:10 +00:00
|
|
|
&& ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
|
|
|
|
|
);
|
2008-04-13 17:37:41 +00:00
|
|
|
}
|
2004-10-24 19:14:48 +00:00
|
|
|
|
2015-05-17 15:36:29 +00:00
|
|
|
/**
|
|
|
|
|
* Check whether to enable new files patrol features for this user
|
|
|
|
|
* @return bool True or false
|
|
|
|
|
*/
|
|
|
|
|
public function useFilePatrol() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$useRCPatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseRCPatrol' );
|
|
|
|
|
$useFilePatrol = MediaWikiServices::getInstance()->getMainConfig()->get( 'UseFilePatrol' );
|
2015-05-17 15:36:29 +00:00
|
|
|
return (
|
2022-01-06 18:44:56 +00:00
|
|
|
( $useRCPatrol || $useFilePatrol )
|
2015-05-17 15:36:29 +00:00
|
|
|
&& ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-06 16:42:16 +00:00
|
|
|
/**
|
|
|
|
|
* Get the WebRequest object to use with this object
|
|
|
|
|
*
|
|
|
|
|
* @return WebRequest
|
|
|
|
|
*/
|
|
|
|
|
public function getRequest() {
|
|
|
|
|
if ( $this->mRequest ) {
|
|
|
|
|
return $this->mRequest;
|
|
|
|
|
}
|
2020-12-15 09:47:15 +00:00
|
|
|
return RequestContext::getMain()->getRequest();
|
2011-07-06 16:42:16 +00:00
|
|
|
}
|
|
|
|
|
|
2017-02-10 14:18:02 +00:00
|
|
|
/**
|
|
|
|
|
* Compute experienced level based on edit count and registration date.
|
|
|
|
|
*
|
2020-12-12 11:24:19 +00:00
|
|
|
* @return string|false 'newcomer', 'learner', or 'experienced', false for anonymous users
|
2017-02-10 14:18:02 +00:00
|
|
|
*/
|
|
|
|
|
public function getExperienceLevel() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$mainConfig = MediaWikiServices::getInstance()->getMainConfig();
|
|
|
|
|
$learnerEdits = $mainConfig->get( 'LearnerEdits' );
|
|
|
|
|
$experiencedUserEdits = $mainConfig->get( 'ExperiencedUserEdits' );
|
|
|
|
|
$learnerMemberSince = $mainConfig->get( 'LearnerMemberSince' );
|
|
|
|
|
$experiencedUserMemberSince = $mainConfig->get( 'ExperiencedUserMemberSince' );
|
2017-02-10 14:18:02 +00:00
|
|
|
if ( $this->isAnon() ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$editCount = $this->getEditCount();
|
|
|
|
|
$registration = $this->getRegistration();
|
|
|
|
|
$now = time();
|
2022-01-06 18:44:56 +00:00
|
|
|
$learnerRegistration = wfTimestamp( TS_MW, $now - $learnerMemberSince * 86400 );
|
|
|
|
|
$experiencedRegistration = wfTimestamp( TS_MW, $now - $experiencedUserMemberSince * 86400 );
|
2020-12-12 11:24:19 +00:00
|
|
|
if ( $registration === null ) {
|
|
|
|
|
// for some very old accounts, this information is missing in the database
|
|
|
|
|
// treat them as old enough to be 'experienced'
|
|
|
|
|
$registration = $experiencedRegistration;
|
|
|
|
|
}
|
2017-02-10 14:18:02 +00:00
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $editCount < $learnerEdits ||
|
2019-02-07 03:28:29 +00:00
|
|
|
$registration > $learnerRegistration ) {
|
2017-02-10 14:18:02 +00:00
|
|
|
return 'newcomer';
|
2019-02-07 03:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $editCount > $experiencedUserEdits &&
|
2017-02-10 14:18:02 +00:00
|
|
|
$registration <= $experiencedRegistration
|
|
|
|
|
) {
|
|
|
|
|
return 'experienced';
|
|
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
return 'learner';
|
2017-02-10 14:18:02 +00:00
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
2016-02-01 20:44:03 +00:00
|
|
|
* Persist this user's session (e.g. set cookies)
|
2011-03-11 20:04:17 +00:00
|
|
|
*
|
2020-12-15 09:47:15 +00:00
|
|
|
* @param WebRequest|null $request WebRequest object to use; the global request
|
|
|
|
|
* will be used if null is passed.
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param bool|null $secure Whether to force secure/insecure cookies or use default
|
2013-11-10 14:38:34 +00:00
|
|
|
* @param bool $rememberMe Whether to add a Token cookie for elongated sessions
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2013-11-10 14:38:34 +00:00
|
|
|
public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2018-06-30 09:43:00 +00:00
|
|
|
if ( $this->mId == 0 ) {
|
2013-03-06 01:17:31 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2015-09-22 14:33:24 +00:00
|
|
|
|
2016-02-01 20:44:03 +00:00
|
|
|
$session = $this->getRequest()->getSession();
|
|
|
|
|
if ( $request && $session->getRequest() !== $request ) {
|
|
|
|
|
$session = $session->sessionWithRequest( $request );
|
2016-02-01 17:28:29 +00:00
|
|
|
}
|
2016-02-01 20:44:03 +00:00
|
|
|
$delay = $session->delaySave();
|
|
|
|
|
|
|
|
|
|
if ( !$session->getUser()->equals( $this ) ) {
|
|
|
|
|
if ( !$session->canSetUser() ) {
|
2021-03-15 15:25:33 +00:00
|
|
|
LoggerFactory::getInstance( 'session' )
|
2016-02-01 20:44:03 +00:00
|
|
|
->warning( __METHOD__ .
|
|
|
|
|
": Cannot save user \"$this\" to a user \"{$session->getUser()}\"'s immutable session"
|
|
|
|
|
);
|
|
|
|
|
return;
|
2008-05-29 07:50:27 +00:00
|
|
|
}
|
2016-02-01 20:44:03 +00:00
|
|
|
$session->setUser( $this );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2012-09-17 18:10:30 +00:00
|
|
|
|
2016-02-01 20:44:03 +00:00
|
|
|
$session->setRememberUser( $rememberMe );
|
|
|
|
|
if ( $secure !== null ) {
|
|
|
|
|
$session->setForceHTTPS( $secure );
|
2012-09-17 18:10:30 +00:00
|
|
|
}
|
2016-02-01 20:44:03 +00:00
|
|
|
|
|
|
|
|
$session->persist();
|
|
|
|
|
|
|
|
|
|
ScopedCallback::consume( $delay );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Log this user out.
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function logout() {
|
2016-12-22 10:31:10 +00:00
|
|
|
// Avoid PHP 7.1 warning of passing $this by reference
|
|
|
|
|
$user = $this;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
if ( $this->getHookRunner()->onUserLogout( $user ) ) {
|
2008-01-08 18:10:58 +00:00
|
|
|
$this->doLogout();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-02-01 20:44:03 +00:00
|
|
|
* Clear the user's session, and reset the instance cache.
|
2008-08-05 13:42:02 +00:00
|
|
|
* @see logout()
|
2008-01-08 18:10:58 +00:00
|
|
|
*/
|
2011-07-18 22:01:40 +00:00
|
|
|
public function doLogout() {
|
2016-02-01 20:44:03 +00:00
|
|
|
$session = $this->getRequest()->getSession();
|
|
|
|
|
if ( !$session->canSetUser() ) {
|
2021-03-15 15:25:33 +00:00
|
|
|
LoggerFactory::getInstance( 'session' )
|
2016-02-01 20:44:03 +00:00
|
|
|
->warning( __METHOD__ . ": Cannot log out of an immutable session" );
|
2016-02-12 21:47:31 +00:00
|
|
|
$error = 'immutable';
|
2016-02-01 20:44:03 +00:00
|
|
|
} elseif ( !$session->getUser()->equals( $this ) ) {
|
2021-03-15 15:25:33 +00:00
|
|
|
LoggerFactory::getInstance( 'session' )
|
2016-02-01 20:44:03 +00:00
|
|
|
->warning( __METHOD__ .
|
|
|
|
|
": Cannot log user \"$this\" out of a user \"{$session->getUser()}\"'s session"
|
|
|
|
|
);
|
|
|
|
|
// But we still may as well make this user object anon
|
|
|
|
|
$this->clearInstanceCache( 'defaults' );
|
2016-02-12 21:47:31 +00:00
|
|
|
$error = 'wronguser';
|
2016-02-01 20:44:03 +00:00
|
|
|
} else {
|
|
|
|
|
$this->clearInstanceCache( 'defaults' );
|
|
|
|
|
$delay = $session->delaySave();
|
2016-02-26 21:17:37 +00:00
|
|
|
$session->unpersist(); // Clear cookies (T127436)
|
2016-02-01 20:44:03 +00:00
|
|
|
$session->setLoggedOutTimestamp( time() );
|
|
|
|
|
$session->setUser( new User );
|
|
|
|
|
$session->set( 'wsUserID', 0 ); // Other code expects this
|
2016-05-31 19:20:05 +00:00
|
|
|
$session->resetAllTokens();
|
2016-02-01 20:44:03 +00:00
|
|
|
ScopedCallback::consume( $delay );
|
2016-02-12 21:47:31 +00:00
|
|
|
$error = false;
|
2016-02-01 20:44:03 +00:00
|
|
|
}
|
2021-03-15 15:25:33 +00:00
|
|
|
LoggerFactory::getInstance( 'authevents' )->info( 'Logout', [
|
2016-02-12 21:47:31 +00:00
|
|
|
'event' => 'logout',
|
|
|
|
|
'successful' => $error === false,
|
|
|
|
|
'status' => $error ?: 'success',
|
|
|
|
|
] );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Save this user's settings into the database.
|
2007-04-04 05:22:37 +00:00
|
|
|
* @todo Only rarely do all these fields need to be set!
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function saveSettings() {
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( wfReadOnly() ) {
|
2015-04-06 18:26:42 +00:00
|
|
|
// @TODO: caller should deal with this instead!
|
|
|
|
|
// This should really just be an exception.
|
|
|
|
|
MWExceptionHandler::logException( new DBExpectedError(
|
|
|
|
|
null,
|
|
|
|
|
"Could not update user with ID '{$this->mId}'; DB is read-only."
|
|
|
|
|
) );
|
|
|
|
|
return;
|
2013-04-20 22:49:30 +00:00
|
|
|
}
|
2015-03-30 19:00:07 +00:00
|
|
|
|
|
|
|
|
$this->load();
|
2018-06-30 09:43:00 +00:00
|
|
|
if ( $this->mId == 0 ) {
|
2015-03-30 20:37:21 +00:00
|
|
|
return; // anon
|
2013-04-20 22:49:30 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2015-03-30 20:37:21 +00:00
|
|
|
// Get a new user_touched that is higher than the old one.
|
|
|
|
|
// This will be used for a CAS check as a last-resort safety
|
2016-09-05 20:21:26 +00:00
|
|
|
// check against race conditions and replica DB lag.
|
2015-04-16 17:54:26 +00:00
|
|
|
$newTouched = $this->newTouchedTimestamp();
|
2015-03-30 20:37:21 +00:00
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$dbw = wfGetDB( DB_PRIMARY );
|
2019-04-30 15:08:48 +00:00
|
|
|
$dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
|
2017-09-12 17:12:29 +00:00
|
|
|
$dbw->update( 'user',
|
|
|
|
|
[ /* SET */
|
|
|
|
|
'user_name' => $this->mName,
|
|
|
|
|
'user_real_name' => $this->mRealName,
|
|
|
|
|
'user_email' => $this->mEmail,
|
|
|
|
|
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
|
|
|
|
|
'user_touched' => $dbw->timestamp( $newTouched ),
|
|
|
|
|
'user_token' => strval( $this->mToken ),
|
|
|
|
|
'user_email_token' => $this->mEmailToken,
|
|
|
|
|
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
|
|
|
|
|
], $this->makeUpdateConditions( $dbw, [ /* WHERE */
|
|
|
|
|
'user_id' => $this->mId,
|
|
|
|
|
] ), $fname
|
|
|
|
|
);
|
2010-01-06 03:42:30 +00:00
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
if ( !$dbw->affectedRows() ) {
|
|
|
|
|
// Maybe the problem was a missed cache update; clear it to be safe
|
|
|
|
|
$this->clearSharedCache( 'refresh' );
|
|
|
|
|
// User was changed in the meantime or loaded with stale data
|
2021-09-01 21:04:40 +00:00
|
|
|
$from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'primary' : 'replica';
|
2018-09-12 17:33:34 +00:00
|
|
|
LoggerFactory::getInstance( 'preferences' )->warning(
|
|
|
|
|
"CAS update failed on user_touched for user ID '{user_id}' ({db_flag} read)",
|
|
|
|
|
[ 'user_id' => $this->mId, 'db_flag' => $from ]
|
|
|
|
|
);
|
|
|
|
|
throw new MWException( "CAS update failed on user_touched. " .
|
|
|
|
|
"The version of the user to be saved is older than the current version."
|
2017-09-12 17:12:29 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 17:40:52 +00:00
|
|
|
$dbw->update(
|
|
|
|
|
'actor',
|
|
|
|
|
[ 'actor_name' => $this->mName ],
|
|
|
|
|
[ 'actor_user' => $this->mId ],
|
|
|
|
|
$fname
|
|
|
|
|
);
|
2021-04-16 04:16:55 +00:00
|
|
|
MediaWikiServices::getInstance()->getActorStore()->deleteUserIdentityFromCache( $this );
|
2017-09-12 17:12:29 +00:00
|
|
|
} );
|
2015-03-30 20:37:21 +00:00
|
|
|
|
2015-04-16 17:54:26 +00:00
|
|
|
$this->mTouched = $newTouched;
|
2021-10-25 19:56:47 +00:00
|
|
|
MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptionsInternal( $this, $dbw );
|
2010-01-06 03:42:30 +00:00
|
|
|
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserSaveSettings( $this );
|
2019-03-19 03:31:54 +00:00
|
|
|
$this->clearSharedCache( 'changed' );
|
2019-03-15 00:23:26 +00:00
|
|
|
$hcu = MediaWikiServices::getInstance()->getHtmlCacheUpdater();
|
|
|
|
|
$hcu->purgeTitleUrls( $this->getUserPage(), $hcu::PURGE_INTENT_TXROUND_REFLECTED );
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2004-09-02 23:28:24 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* If only this user's username is known, and it exists, return the user ID.
|
2015-07-10 23:18:23 +00:00
|
|
|
*
|
|
|
|
|
* @param int $flags Bitfield of User:READ_* constants; useful for existence checks
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return int
|
2004-09-02 23:28:24 +00:00
|
|
|
*/
|
2020-09-09 19:58:16 +00:00
|
|
|
public function idForName( $flags = self::READ_NORMAL ) {
|
2005-09-05 02:22:20 +00:00
|
|
|
$s = trim( $this->getName() );
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( $s === '' ) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2004-08-14 22:30:10 +00:00
|
|
|
|
2020-09-09 19:58:16 +00:00
|
|
|
list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
|
|
|
|
|
$db = wfGetDB( $index );
|
2015-07-10 23:18:23 +00:00
|
|
|
|
2015-07-14 18:13:01 +00:00
|
|
|
$id = $db->selectField( 'user',
|
2016-02-17 09:09:32 +00:00
|
|
|
'user_id', [ 'user_name' => $s ], __METHOD__, $options );
|
2015-07-10 23:18:23 +00:00
|
|
|
|
2015-07-14 18:13:01 +00:00
|
|
|
return (int)$id;
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
|
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2006-10-14 06:58:19 +00:00
|
|
|
* Add a user to the database, return the user object
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $name Username to add
|
2014-05-11 15:34:14 +00:00
|
|
|
* @param array $params Array of Strings Non-default parameters to save to
|
|
|
|
|
* the database as user_* fields:
|
|
|
|
|
* - email: The user's email address.
|
|
|
|
|
* - email_authenticated: The email authentication timestamp.
|
|
|
|
|
* - real_name: The user's real name.
|
|
|
|
|
* - options: An associative array of non-default options.
|
|
|
|
|
* - token: Random authentication token. Do not set.
|
|
|
|
|
* - registration: Registration timestamp. Do not set.
|
|
|
|
|
* @return User|null User object, or null if the username already exists.
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
public static function createNew( $name, $params = [] ) {
|
2021-04-29 16:24:12 +00:00
|
|
|
return self::insertNewUser( static function ( UserIdentity $actor, IDatabase $dbw ) {
|
2021-04-07 17:32:17 +00:00
|
|
|
return MediaWikiServices::getInstance()->getActorStore()->createNewActor( $actor, $dbw );
|
|
|
|
|
}, $name, $params );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* See ::createNew
|
|
|
|
|
* @param callable $insertActor ( UserIdentity $actor, IDatabase $dbw ): int actor ID,
|
|
|
|
|
* @param string $name
|
|
|
|
|
* @param array $params
|
|
|
|
|
* @return User|null
|
|
|
|
|
*/
|
|
|
|
|
private static function insertNewUser( callable $insertActor, $name, $params = [] ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
foreach ( [ 'password', 'newpassword', 'newpass_time', 'password_expires' ] as $field ) {
|
2015-09-04 16:17:42 +00:00
|
|
|
if ( isset( $params[$field] ) ) {
|
|
|
|
|
wfDeprecated( __METHOD__ . " with param '$field'", '1.27' );
|
|
|
|
|
unset( $params[$field] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
$user = new User;
|
|
|
|
|
$user->load();
|
2013-02-27 00:17:58 +00:00
|
|
|
$user->setToken(); // init token
|
2006-10-14 06:58:19 +00:00
|
|
|
if ( isset( $params['options'] ) ) {
|
2020-01-17 06:21:28 +00:00
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getUserOptionsManager()
|
|
|
|
|
->loadUserOptions( $user, $user->queryFlagsUsed, $params['options'] );
|
2006-10-14 06:58:19 +00:00
|
|
|
unset( $params['options'] );
|
|
|
|
|
}
|
2021-04-29 02:37:11 +00:00
|
|
|
$dbw = wfGetDB( DB_PRIMARY );
|
2011-02-16 19:51:25 +00:00
|
|
|
|
2015-09-04 16:17:42 +00:00
|
|
|
$noPass = PasswordFactory::newInvalidPassword()->toString();
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
$fields = [
|
2006-10-14 06:58:19 +00:00
|
|
|
'user_name' => $name,
|
2015-09-04 16:17:42 +00:00
|
|
|
'user_password' => $noPass,
|
|
|
|
|
'user_newpassword' => $noPass,
|
2006-10-14 06:58:19 +00:00
|
|
|
'user_email' => $user->mEmail,
|
|
|
|
|
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
|
|
|
|
|
'user_real_name' => $user->mRealName,
|
2012-03-20 05:17:40 +00:00
|
|
|
'user_token' => strval( $user->mToken ),
|
2006-10-14 06:58:19 +00:00
|
|
|
'user_registration' => $dbw->timestamp( $user->mRegistration ),
|
2006-12-22 23:46:08 +00:00
|
|
|
'user_editcount' => 0,
|
2015-04-06 18:12:41 +00:00
|
|
|
'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2006-10-14 06:58:19 +00:00
|
|
|
foreach ( $params as $name => $value ) {
|
|
|
|
|
$fields["user_$name"] = $value;
|
|
|
|
|
}
|
2017-09-12 17:12:29 +00:00
|
|
|
|
2021-04-07 17:32:17 +00:00
|
|
|
return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields, $insertActor ) {
|
2017-09-12 17:12:29 +00:00
|
|
|
$dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
|
|
|
|
|
if ( $dbw->affectedRows() ) {
|
|
|
|
|
$newUser = self::newFromId( $dbw->insertId() );
|
2018-09-18 18:21:20 +00:00
|
|
|
$newUser->mName = $fields['user_name'];
|
2021-04-07 17:32:17 +00:00
|
|
|
// Don't pass $this, since calling ::getId, ::getName might force ::load
|
|
|
|
|
// and this user might not be ready for the yet.
|
|
|
|
|
$newUser->mActorId = $insertActor( new UserIdentityValue( $newUser->mId, $newUser->mName ), $dbw );
|
2021-09-01 21:04:40 +00:00
|
|
|
// Load the user from primary DB to avoid replica lag
|
2018-02-23 18:24:47 +00:00
|
|
|
$newUser->load( self::READ_LATEST );
|
2017-09-12 17:12:29 +00:00
|
|
|
} else {
|
|
|
|
|
$newUser = null;
|
|
|
|
|
}
|
|
|
|
|
return $newUser;
|
|
|
|
|
} );
|
2006-10-14 06:58:19 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2012-10-19 20:03:05 +00:00
|
|
|
* Add this existing user object to the database. If the user already
|
|
|
|
|
* exists, a fatal status object is returned, and the user object is
|
2012-10-08 22:45:03 +00:00
|
|
|
* initialised with the data from the database.
|
|
|
|
|
*
|
|
|
|
|
* Previously, this function generated a DB error due to a key conflict
|
|
|
|
|
* if the user already existed. Many extension callers use this function
|
|
|
|
|
* in code along the lines of:
|
|
|
|
|
*
|
|
|
|
|
* $user = User::newFromName( $name );
|
2020-12-17 23:10:11 +00:00
|
|
|
* if ( !$user->isRegistered() ) {
|
2012-10-08 22:45:03 +00:00
|
|
|
* $user->addToDatabase();
|
|
|
|
|
* }
|
|
|
|
|
* // do something with $user...
|
|
|
|
|
*
|
2017-02-20 22:44:19 +00:00
|
|
|
* However, this was vulnerable to a race condition (T18020). By
|
2012-10-08 22:45:03 +00:00
|
|
|
* initialising the user object if the user exists, we aim to support this
|
|
|
|
|
* calling sequence as far as possible.
|
|
|
|
|
*
|
|
|
|
|
* Note that if the user exists, this function will acquire a write lock,
|
2020-12-17 23:10:11 +00:00
|
|
|
* so it is still advisable to make the call conditional on isRegistered(),
|
2012-10-08 22:45:03 +00:00
|
|
|
* and to commit the transaction after calling.
|
|
|
|
|
*
|
2012-12-09 03:09:48 +00:00
|
|
|
* @throws MWException
|
2012-10-08 22:45:03 +00:00
|
|
|
* @return Status
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function addToDatabase() {
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2013-02-27 00:17:58 +00:00
|
|
|
if ( !$this->mToken ) {
|
|
|
|
|
$this->setToken(); // init token
|
|
|
|
|
}
|
2012-03-12 21:17:23 +00:00
|
|
|
|
2017-05-18 20:16:55 +00:00
|
|
|
if ( !is_string( $this->mName ) ) {
|
|
|
|
|
throw new RuntimeException( "User name field is not set." );
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-06 18:12:41 +00:00
|
|
|
$this->mTouched = $this->newTouchedTimestamp();
|
2012-03-12 21:17:23 +00:00
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$dbw = wfGetDB( DB_PRIMARY );
|
2019-04-30 15:08:48 +00:00
|
|
|
$status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
|
2017-09-12 17:12:29 +00:00
|
|
|
$noPass = PasswordFactory::newInvalidPassword()->toString();
|
|
|
|
|
$dbw->insert( 'user',
|
|
|
|
|
[
|
|
|
|
|
'user_name' => $this->mName,
|
|
|
|
|
'user_password' => $noPass,
|
|
|
|
|
'user_newpassword' => $noPass,
|
|
|
|
|
'user_email' => $this->mEmail,
|
|
|
|
|
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
|
|
|
|
|
'user_real_name' => $this->mRealName,
|
|
|
|
|
'user_token' => strval( $this->mToken ),
|
|
|
|
|
'user_registration' => $dbw->timestamp( $this->mRegistration ),
|
|
|
|
|
'user_editcount' => 0,
|
|
|
|
|
'user_touched' => $dbw->timestamp( $this->mTouched ),
|
|
|
|
|
], $fname,
|
|
|
|
|
[ 'IGNORE' ]
|
2016-08-21 21:03:24 +00:00
|
|
|
);
|
2017-09-12 17:12:29 +00:00
|
|
|
if ( !$dbw->affectedRows() ) {
|
|
|
|
|
// Use locking reads to bypass any REPEATABLE-READ snapshot.
|
|
|
|
|
$this->mId = $dbw->selectField(
|
|
|
|
|
'user',
|
|
|
|
|
'user_id',
|
|
|
|
|
[ 'user_name' => $this->mName ],
|
2018-09-30 13:22:29 +00:00
|
|
|
$fname,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'LOCK IN SHARE MODE' ]
|
|
|
|
|
);
|
|
|
|
|
$loaded = false;
|
2021-03-10 19:40:33 +00:00
|
|
|
if ( $this->mId && $this->loadFromDatabase( IDBAccessObject::READ_LOCKING ) ) {
|
2019-03-29 20:12:24 +00:00
|
|
|
$loaded = true;
|
2012-10-08 22:45:03 +00:00
|
|
|
}
|
2017-09-12 17:12:29 +00:00
|
|
|
if ( !$loaded ) {
|
2018-09-30 13:22:29 +00:00
|
|
|
throw new MWException( $fname . ": hit a key conflict attempting " .
|
2017-09-12 17:12:29 +00:00
|
|
|
"to insert user '{$this->mName}' row, but it was not present in select!" );
|
|
|
|
|
}
|
|
|
|
|
return Status::newFatal( 'userexists' );
|
2012-10-08 22:45:03 +00:00
|
|
|
}
|
2017-09-12 17:12:29 +00:00
|
|
|
$this->mId = $dbw->insertId();
|
|
|
|
|
|
2021-04-07 17:32:17 +00:00
|
|
|
// Don't pass $this, since calling ::getId, ::getName might force ::load
|
|
|
|
|
// and this user might not be ready for the yet.
|
|
|
|
|
$this->mActorId = MediaWikiServices::getInstance()
|
|
|
|
|
->getActorNormalization()
|
|
|
|
|
->acquireActorId( new UserIdentityValue( $this->mId, $this->mName ), $dbw );
|
2017-09-12 17:12:29 +00:00
|
|
|
return Status::newGood();
|
|
|
|
|
} );
|
|
|
|
|
if ( !$status->isGood() ) {
|
|
|
|
|
return $status;
|
2012-10-08 22:45:03 +00:00
|
|
|
}
|
2006-10-14 06:58:19 +00:00
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
// Clear instance cache other than user table data and actor, which is already accurate
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->clearInstanceCache();
|
2010-01-06 03:42:30 +00:00
|
|
|
|
2020-01-17 06:21:28 +00:00
|
|
|
MediaWikiServices::getInstance()->getUserOptionsManager()->saveOptions( $this );
|
2012-10-08 22:45:03 +00:00
|
|
|
return Status::newGood();
|
2003-04-14 23:10:40 +00:00
|
|
|
}
|
2003-08-31 14:30:24 +00:00
|
|
|
|
2006-10-14 06:58:19 +00:00
|
|
|
/**
|
2011-10-08 20:22:53 +00:00
|
|
|
* If this user is logged-in and blocked,
|
|
|
|
|
* block any IP address they've successfully logged in from.
|
|
|
|
|
* @return bool A block was spread
|
2006-10-14 06:58:19 +00:00
|
|
|
*/
|
2011-10-08 20:22:53 +00:00
|
|
|
public function spreadAnyEditBlock() {
|
2020-12-17 23:10:11 +00:00
|
|
|
if ( $this->isRegistered() && $this->getBlock() ) {
|
2011-10-08 20:22:53 +00:00
|
|
|
return $this->spreadBlock();
|
|
|
|
|
}
|
2016-03-18 01:16:18 +00:00
|
|
|
|
2011-10-08 20:22:53 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If this (non-anonymous) user is blocked,
|
|
|
|
|
* block the IP address they've successfully logged in from.
|
|
|
|
|
* @return bool A block was spread
|
|
|
|
|
*/
|
|
|
|
|
protected function spreadBlock() {
|
2020-06-01 05:00:39 +00:00
|
|
|
wfDebug( __METHOD__ . "()" );
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2003-09-01 13:13:56 +00:00
|
|
|
if ( $this->mId == 0 ) {
|
2011-10-08 20:22:53 +00:00
|
|
|
return false;
|
2003-08-31 14:30:24 +00:00
|
|
|
}
|
2004-08-14 22:30:10 +00:00
|
|
|
|
2019-05-13 14:18:07 +00:00
|
|
|
$userblock = DatabaseBlock::newFromTarget( $this->getName() );
|
2006-07-10 06:30:03 +00:00
|
|
|
if ( !$userblock ) {
|
2011-10-08 20:22:53 +00:00
|
|
|
return false;
|
2003-08-31 14:30:24 +00:00
|
|
|
}
|
2004-08-14 22:30:10 +00:00
|
|
|
|
2011-10-08 20:22:53 +00:00
|
|
|
return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
|
2003-08-31 14:30:24 +00:00
|
|
|
}
|
2003-09-10 01:18:23 +00:00
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get whether the user is explicitly blocked from account creation.
|
2021-04-21 16:25:17 +00:00
|
|
|
* @deprecated since 1.37. Instead use Authority::authorize* for createaccount permission.
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
* @return bool|AbstractBlock
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isBlockedFromCreateAccount() {
|
2006-07-10 06:30:03 +00:00
|
|
|
$this->getBlockedStatus();
|
Separate out different functionalities of Block::prevents
Block::prevents plays several different roles:
* acts as get/setter for Boolean properties that correspond to
ipb_create_account, ipb_block_email and ipb_allow_usertalk
* calculates whether a block blocks a given right, based on Block
properties, global configs, white/blacklists and anonymous user
rights
* decides whether a block prevents editing of the target's own
user talk page (listed separately because 'editownusertalk' is
not a right)
This patch:
* renames mDisableUsertalk to allowEditUsertalk (and reverses the
value), to match the field ipb_allow_usertalk and make this logic
easier to follow
* renames mCreateAccount to blockCreateAccount, to make it clear
that the flag blocks account creation when true, and make this
logic easier to follow
* decouples the block that is stored in the database (which now
reflects the form that the admin submitted) and the behaviour of
the block on enforcement (since the properties set by the admin
can be overridden by global configs) - so if the global configs
change, the block behaviour could too
* creates get/setters for blockCreateAccount, mBlockEmail and
allowEditUsertalk properties
* creates appliesToRight, exclusively for checking whether the
block blocks a given right, taking into account the block
properties, global configs and anonymous user rights
* creates appliesToUsertalk, for checking whether the block
blocks a user from editing their own talk page. The block is
unaware of the user trying to make the edit, and this user is not
always the same as the block target, e.g. if the block target is
an IP range. Therefore the user's talk page is passed in to this
method. appliesToUsertalk can be called from anywhere where the
user is known
* uses the get/setters wherever Block::prevents was being used as
such
* uses appliesToRight whenever Block::prevents was being used to
determine if the block blocks a given right
* uses appliesToUsertalk in User::isBlockedFrom
Bug: T211578
Bug: T214508
Change-Id: I0e131696419211319082cb454f4f05297e55d22e
2019-02-09 12:17:54 +00:00
|
|
|
if ( $this->mBlock && $this->mBlock->appliesToRight( 'createaccount' ) ) {
|
2011-03-21 19:12:41 +00:00
|
|
|
return $this->mBlock;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-20 22:44:19 +00:00
|
|
|
# T15611: if the IP address the user is trying to create an account from is
|
2011-03-21 19:12:41 +00:00
|
|
|
# blocked with createaccount disabled, prevent new account creation there even
|
|
|
|
|
# when the user is logged in
|
2013-01-12 18:21:04 +00:00
|
|
|
if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
|
2019-05-13 14:18:07 +00:00
|
|
|
$this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
|
|
|
|
|
null, $this->getRequest()->getIP()
|
|
|
|
|
);
|
2011-03-21 19:12:41 +00:00
|
|
|
}
|
Separate Block into AbstractBlock, Block and SystemBlock
This commit splits the existing Block class into AbstractBlock, Block
and SystemBlock.
Before this patch, the Block class represents several types of
blocks, which can be separated into blocks stored in the database,
and temporary blocks created by the system. These are now
represented by Block and SystemBlock, which inherit from
AbstractBlock.
This lays the foundations for:
* enforcing block parameters from multiple blocks that apply to a
user/IP address
* improvements to the Block API, including the addition of services
Breaking changes: functions expecting a Block object should still
expect a Block object if it came from the database, but other
functions may now need to expect an AbstractBlock or SystemBlock
object. (Note that an alternative naming scheme, in which the
abstract class is called Block and the subclasses are DatabaseBlock
and SystemBlock, avoids this breakage. However, it introduces more
breakages to calls to static Block methods and new Block
instantiations.)
Changes to tests: system blocks don't set the $blockCreateAccount or
$mExipry block properties, so remove/change any tests that assume
they do.
Bug: T222737
Change-Id: I83bceb5e5049e254c90ace060f8f8fad44696c67
2019-03-18 22:09:49 +00:00
|
|
|
return $this->mBlockedFromCreateAccount instanceof AbstractBlock
|
Separate out different functionalities of Block::prevents
Block::prevents plays several different roles:
* acts as get/setter for Boolean properties that correspond to
ipb_create_account, ipb_block_email and ipb_allow_usertalk
* calculates whether a block blocks a given right, based on Block
properties, global configs, white/blacklists and anonymous user
rights
* decides whether a block prevents editing of the target's own
user talk page (listed separately because 'editownusertalk' is
not a right)
This patch:
* renames mDisableUsertalk to allowEditUsertalk (and reverses the
value), to match the field ipb_allow_usertalk and make this logic
easier to follow
* renames mCreateAccount to blockCreateAccount, to make it clear
that the flag blocks account creation when true, and make this
logic easier to follow
* decouples the block that is stored in the database (which now
reflects the form that the admin submitted) and the behaviour of
the block on enforcement (since the properties set by the admin
can be overridden by global configs) - so if the global configs
change, the block behaviour could too
* creates get/setters for blockCreateAccount, mBlockEmail and
allowEditUsertalk properties
* creates appliesToRight, exclusively for checking whether the
block blocks a given right, taking into account the block
properties, global configs and anonymous user rights
* creates appliesToUsertalk, for checking whether the block
blocks a user from editing their own talk page. The block is
unaware of the user trying to make the edit, and this user is not
always the same as the block target, e.g. if the block target is
an IP range. Therefore the user's talk page is passed in to this
method. appliesToUsertalk can be called from anywhere where the
user is known
* uses the get/setters wherever Block::prevents was being used as
such
* uses appliesToRight whenever Block::prevents was being used to
determine if the block blocks a given right
* uses appliesToUsertalk in User::isBlockedFrom
Bug: T211578
Bug: T214508
Change-Id: I0e131696419211319082cb454f4f05297e55d22e
2019-02-09 12:17:54 +00:00
|
|
|
&& $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
|
2011-06-26 23:01:29 +00:00
|
|
|
? $this->mBlockedFromCreateAccount
|
2011-03-21 19:12:41 +00:00
|
|
|
: false;
|
2006-07-10 06:30:03 +00:00
|
|
|
}
|
|
|
|
|
|
2007-06-07 17:31:08 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get whether the user is blocked from using Special:Emailuser.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2007-06-07 17:31:08 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isBlockedFromEmailuser() {
|
2007-06-07 17:31:08 +00:00
|
|
|
$this->getBlockedStatus();
|
Separate out different functionalities of Block::prevents
Block::prevents plays several different roles:
* acts as get/setter for Boolean properties that correspond to
ipb_create_account, ipb_block_email and ipb_allow_usertalk
* calculates whether a block blocks a given right, based on Block
properties, global configs, white/blacklists and anonymous user
rights
* decides whether a block prevents editing of the target's own
user talk page (listed separately because 'editownusertalk' is
not a right)
This patch:
* renames mDisableUsertalk to allowEditUsertalk (and reverses the
value), to match the field ipb_allow_usertalk and make this logic
easier to follow
* renames mCreateAccount to blockCreateAccount, to make it clear
that the flag blocks account creation when true, and make this
logic easier to follow
* decouples the block that is stored in the database (which now
reflects the form that the admin submitted) and the behaviour of
the block on enforcement (since the properties set by the admin
can be overridden by global configs) - so if the global configs
change, the block behaviour could too
* creates get/setters for blockCreateAccount, mBlockEmail and
allowEditUsertalk properties
* creates appliesToRight, exclusively for checking whether the
block blocks a given right, taking into account the block
properties, global configs and anonymous user rights
* creates appliesToUsertalk, for checking whether the block
blocks a user from editing their own talk page. The block is
unaware of the user trying to make the edit, and this user is not
always the same as the block target, e.g. if the block target is
an IP range. Therefore the user's talk page is passed in to this
method. appliesToUsertalk can be called from anywhere where the
user is known
* uses the get/setters wherever Block::prevents was being used as
such
* uses appliesToRight whenever Block::prevents was being used to
determine if the block blocks a given right
* uses appliesToUsertalk in User::isBlockedFrom
Bug: T211578
Bug: T214508
Change-Id: I0e131696419211319082cb454f4f05297e55d22e
2019-02-09 12:17:54 +00:00
|
|
|
return $this->mBlock && $this->mBlock->appliesToRight( 'sendemail' );
|
2007-06-07 17:31:08 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-27 01:45:18 +00:00
|
|
|
/**
|
|
|
|
|
* Get whether the user is blocked from using Special:Upload
|
|
|
|
|
*
|
2019-01-08 12:44:33 +00:00
|
|
|
* @since 1.33
|
2018-08-27 01:45:18 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function isBlockedFromUpload() {
|
|
|
|
|
$this->getBlockedStatus();
|
Separate out different functionalities of Block::prevents
Block::prevents plays several different roles:
* acts as get/setter for Boolean properties that correspond to
ipb_create_account, ipb_block_email and ipb_allow_usertalk
* calculates whether a block blocks a given right, based on Block
properties, global configs, white/blacklists and anonymous user
rights
* decides whether a block prevents editing of the target's own
user talk page (listed separately because 'editownusertalk' is
not a right)
This patch:
* renames mDisableUsertalk to allowEditUsertalk (and reverses the
value), to match the field ipb_allow_usertalk and make this logic
easier to follow
* renames mCreateAccount to blockCreateAccount, to make it clear
that the flag blocks account creation when true, and make this
logic easier to follow
* decouples the block that is stored in the database (which now
reflects the form that the admin submitted) and the behaviour of
the block on enforcement (since the properties set by the admin
can be overridden by global configs) - so if the global configs
change, the block behaviour could too
* creates get/setters for blockCreateAccount, mBlockEmail and
allowEditUsertalk properties
* creates appliesToRight, exclusively for checking whether the
block blocks a given right, taking into account the block
properties, global configs and anonymous user rights
* creates appliesToUsertalk, for checking whether the block
blocks a user from editing their own talk page. The block is
unaware of the user trying to make the edit, and this user is not
always the same as the block target, e.g. if the block target is
an IP range. Therefore the user's talk page is passed in to this
method. appliesToUsertalk can be called from anywhere where the
user is known
* uses the get/setters wherever Block::prevents was being used as
such
* uses appliesToRight whenever Block::prevents was being used to
determine if the block blocks a given right
* uses appliesToUsertalk in User::isBlockedFrom
Bug: T211578
Bug: T214508
Change-Id: I0e131696419211319082cb454f4f05297e55d22e
2019-02-09 12:17:54 +00:00
|
|
|
return $this->mBlock && $this->mBlock->appliesToRight( 'upload' );
|
2018-08-27 01:45:18 +00:00
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get whether the user is allowed to create an account.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2014-02-21 09:58:36 +00:00
|
|
|
public function isAllowedToCreateAccount() {
|
2006-07-10 06:30:03 +00:00
|
|
|
return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
|
2003-09-10 01:18:23 +00:00
|
|
|
}
|
|
|
|
|
|
2005-02-21 11:28:07 +00:00
|
|
|
/**
|
|
|
|
|
* Get this user's personal page title.
|
|
|
|
|
*
|
2014-04-23 09:41:35 +00:00
|
|
|
* @return Title User's personal page title
|
2005-02-21 11:28:07 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getUserPage() {
|
2005-09-05 02:22:20 +00:00
|
|
|
return Title::makeTitle( NS_USER, $this->getName() );
|
2004-04-11 01:25:00 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-02-21 11:28:07 +00:00
|
|
|
/**
|
|
|
|
|
* Get this user's talk page title.
|
|
|
|
|
*
|
2021-06-17 14:32:05 +00:00
|
|
|
* @return Title
|
2005-02-21 11:28:07 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function getTalkPage() {
|
2005-02-21 11:28:07 +00:00
|
|
|
$title = $this->getUserPage();
|
|
|
|
|
return $title->getTalkPage();
|
|
|
|
|
}
|
2004-06-26 01:48:39 +00:00
|
|
|
|
2004-09-04 13:06:25 +00:00
|
|
|
/**
|
2004-09-04 14:21:45 +00:00
|
|
|
* Determine whether the user is a newbie. Newbies are either
|
2005-12-23 01:27:27 +00:00
|
|
|
* anonymous IPs, or the most recently created accounts.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2004-09-04 13:06:25 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isNewbie() {
|
2005-12-22 05:41:06 +00:00
|
|
|
return !$this->isAllowed( 'autoconfirmed' );
|
2004-06-26 01:48:39 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2011-10-19 19:38:17 +00:00
|
|
|
/**
|
2016-02-01 20:44:03 +00:00
|
|
|
* Initialize (if necessary) and return a session token value
|
|
|
|
|
* which can be used in edit forms to show that the user's
|
|
|
|
|
* login credentials aren't being hijacked with a foreign form
|
|
|
|
|
* submission.
|
2011-10-19 19:38:17 +00:00
|
|
|
*
|
2016-02-01 20:44:03 +00:00
|
|
|
* @since 1.27
|
2021-06-02 22:13:53 +00:00
|
|
|
* @deprecated since 1.37. Use CsrfTokenSet::getToken instead
|
2020-10-28 10:01:33 +00:00
|
|
|
* @param string|string[] $salt Optional function-specific data for hashing
|
2020-12-15 09:47:15 +00:00
|
|
|
* @param WebRequest|null $request WebRequest object to use, or null to use the global request
|
2016-04-06 22:22:33 +00:00
|
|
|
* @return MediaWiki\Session\Token The new edit token
|
2005-02-15 00:28:55 +00:00
|
|
|
*/
|
2016-02-01 20:44:03 +00:00
|
|
|
public function getEditTokenObject( $salt = '', $request = null ) {
|
2007-06-23 10:15:10 +00:00
|
|
|
if ( $this->isAnon() ) {
|
2016-02-01 20:44:03 +00:00
|
|
|
return new LoggedOutEditToken();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !$request ) {
|
|
|
|
|
$request = $this->getRequest();
|
2005-02-15 00:28:55 +00:00
|
|
|
}
|
2016-02-01 20:44:03 +00:00
|
|
|
return $request->getSession()->getToken( $salt );
|
2005-02-15 00:28:55 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2014-08-26 18:12:32 +00:00
|
|
|
/**
|
|
|
|
|
* Initialize (if necessary) and return a session token value
|
|
|
|
|
* which can be used in edit forms to show that the user's
|
|
|
|
|
* login credentials aren't being hijacked with a foreign form
|
|
|
|
|
* submission.
|
|
|
|
|
*
|
2016-08-16 22:11:35 +00:00
|
|
|
* The $salt for 'edit' and 'csrf' tokens is the default (empty string).
|
|
|
|
|
*
|
2014-08-26 18:12:32 +00:00
|
|
|
* @since 1.19
|
2021-06-02 22:13:53 +00:00
|
|
|
* @deprecated since 1.37. Use CsrfTokenSet::getToken instead
|
2020-10-28 10:01:33 +00:00
|
|
|
* @param string|string[] $salt Optional function-specific data for hashing
|
2020-12-15 09:47:15 +00:00
|
|
|
* @param WebRequest|null $request WebRequest object to use, or null to use the global request
|
2014-08-26 18:12:32 +00:00
|
|
|
* @return string The new edit token
|
|
|
|
|
*/
|
|
|
|
|
public function getEditToken( $salt = '', $request = null ) {
|
2016-02-01 20:44:03 +00:00
|
|
|
return $this->getEditTokenObject( $salt, $request )->toString();
|
2014-08-26 18:12:32 +00:00
|
|
|
}
|
|
|
|
|
|
2005-02-15 00:28:55 +00:00
|
|
|
/**
|
|
|
|
|
* Check given value against the token value stored in the session.
|
|
|
|
|
* A match should confirm that the form was submitted from the
|
|
|
|
|
* user's own login session, not a form submission from a third-party
|
|
|
|
|
* site.
|
|
|
|
|
*
|
2021-06-02 22:13:53 +00:00
|
|
|
* @deprecated since 1.37. Use CsrfTokenSet::matchToken instead
|
2022-03-01 21:42:03 +00:00
|
|
|
* @param string|null $val Input value to compare
|
2018-04-20 08:16:55 +00:00
|
|
|
* @param string|array $salt Optional function-specific data for hashing
|
2020-12-15 09:47:15 +00:00
|
|
|
* @param WebRequest|null $request Object to use, or null to use the global request
|
2018-06-26 21:14:43 +00:00
|
|
|
* @param int|null $maxage Fail tokens older than this, in seconds
|
2014-04-23 09:41:35 +00:00
|
|
|
* @return bool Whether the token matches
|
2005-02-15 00:28:55 +00:00
|
|
|
*/
|
2014-08-26 18:12:32 +00:00
|
|
|
public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
|
2016-02-01 20:44:03 +00:00
|
|
|
return $this->getEditTokenObject( $salt, $request )->match( $val, $maxage );
|
2005-02-15 00:28:55 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
2008-03-25 22:03:00 +00:00
|
|
|
* Generate a new e-mail confirmation token and send a confirmation/invalidation
|
2005-04-25 18:38:43 +00:00
|
|
|
* mail to the user's given address.
|
|
|
|
|
*
|
2014-04-23 09:41:35 +00:00
|
|
|
* @param string $type Message to send, either "created", "changed" or "set"
|
|
|
|
|
* @return Status
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function sendConfirmationMail( $type = 'created' ) {
|
2008-03-28 20:17:55 +00:00
|
|
|
global $wgLang;
|
2006-11-29 11:43:58 +00:00
|
|
|
$expiration = null; // gets passed-by-ref and defined in next line.
|
2008-03-25 22:03:00 +00:00
|
|
|
$token = $this->confirmationToken( $expiration );
|
|
|
|
|
$url = $this->confirmationTokenUrl( $token );
|
|
|
|
|
$invalidateURL = $this->invalidationTokenUrl( $token );
|
2008-04-22 23:47:27 +00:00
|
|
|
$this->saveSettings();
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2011-01-29 09:32:02 +00:00
|
|
|
if ( $type == 'created' || $type === false ) {
|
|
|
|
|
$message = 'confirmemail_body';
|
2019-02-27 02:16:49 +00:00
|
|
|
$type = 'created';
|
2011-01-29 09:32:02 +00:00
|
|
|
} elseif ( $type === true ) {
|
|
|
|
|
$message = 'confirmemail_body_changed';
|
2019-02-27 02:16:49 +00:00
|
|
|
$type = 'changed';
|
2011-01-29 09:32:02 +00:00
|
|
|
} else {
|
2013-08-28 00:38:27 +00:00
|
|
|
// Messages: confirmemail_body_changed, confirmemail_body_set
|
2011-01-29 09:32:02 +00:00
|
|
|
$message = 'confirmemail_body_' . $type;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-27 02:16:49 +00:00
|
|
|
$mail = [
|
|
|
|
|
'subject' => wfMessage( 'confirmemail_subject' )->text(),
|
|
|
|
|
'body' => wfMessage( $message,
|
2011-08-18 20:03:30 +00:00
|
|
|
$this->getRequest()->getIP(),
|
2005-04-25 18:38:43 +00:00
|
|
|
$this->getName(),
|
|
|
|
|
$url,
|
2015-11-30 20:38:12 +00:00
|
|
|
$wgLang->userTimeAndDate( $expiration, $this ),
|
2009-06-01 20:57:11 +00:00
|
|
|
$invalidateURL,
|
2015-11-30 20:38:12 +00:00
|
|
|
$wgLang->userDate( $expiration, $this ),
|
2019-02-27 02:16:49 +00:00
|
|
|
$wgLang->userTime( $expiration, $this ) )->text(),
|
|
|
|
|
'from' => null,
|
|
|
|
|
'replyTo' => null,
|
|
|
|
|
];
|
|
|
|
|
$info = [
|
|
|
|
|
'type' => $type,
|
|
|
|
|
'ip' => $this->getRequest()->getIP(),
|
|
|
|
|
'confirmURL' => $url,
|
|
|
|
|
'invalidateURL' => $invalidateURL,
|
|
|
|
|
'expiration' => $expiration
|
|
|
|
|
];
|
|
|
|
|
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserSendConfirmationMail( $this, $mail, $info );
|
2019-02-27 02:16:49 +00:00
|
|
|
return $this->sendMail( $mail['subject'], $mail['body'], $mail['from'], $mail['replyTo'] );
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
|
|
|
|
* Send an e-mail to this user's account. Does not check for
|
|
|
|
|
* confirmed status or validity.
|
|
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $subject Message subject
|
|
|
|
|
* @param string $body Message body
|
2015-07-13 21:18:55 +00:00
|
|
|
* @param User|null $from Optional sending user; if unspecified, default
|
2014-05-11 15:34:14 +00:00
|
|
|
* $wgPasswordSender will be used.
|
2019-02-27 02:16:49 +00:00
|
|
|
* @param MailAddress|null $replyto Reply-To address
|
2011-01-06 15:55:56 +00:00
|
|
|
* @return Status
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function sendMail( $subject, $body, $from = null, $replyto = null ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$passwordSender = MediaWikiServices::getInstance()->getMainConfig()->get( 'PasswordSender' );
|
2015-07-13 21:18:55 +00:00
|
|
|
|
|
|
|
|
if ( $from instanceof User ) {
|
|
|
|
|
$sender = MailAddress::newFromUser( $from );
|
|
|
|
|
} else {
|
2022-01-06 18:44:56 +00:00
|
|
|
$sender = new MailAddress( $passwordSender,
|
2013-12-31 06:12:09 +00:00
|
|
|
wfMessage( 'emailsender' )->inContentLanguage()->text() );
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2014-09-13 03:25:19 +00:00
|
|
|
$to = MailAddress::newFromUser( $this );
|
2015-07-13 21:18:55 +00:00
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return UserMailer::send( $to, $sender, $subject, $body, [
|
2015-07-12 05:07:18 +00:00
|
|
|
'replyTo' => $replyto,
|
2016-02-17 09:09:32 +00:00
|
|
|
] );
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
|
|
|
|
* Generate, store, and return a new e-mail confirmation code.
|
2008-08-05 13:42:02 +00:00
|
|
|
* A hash (unsalted, since it's used as a key) is stored.
|
2008-04-15 09:04:45 +00:00
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @note Call saveSettings() after calling this function to commit
|
2008-04-15 09:04:45 +00:00
|
|
|
* this change to the database.
|
|
|
|
|
*
|
2014-04-23 09:41:35 +00:00
|
|
|
* @param string &$expiration Accepts the expiration time
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string New token
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2013-05-16 22:18:57 +00:00
|
|
|
protected function confirmationToken( &$expiration ) {
|
2022-01-06 18:44:56 +00:00
|
|
|
$userEmailConfirmationTokenExpiry = MediaWikiServices::getInstance()
|
|
|
|
|
->getMainConfig()->get( 'UserEmailConfirmationTokenExpiry' );
|
2005-04-25 18:38:43 +00:00
|
|
|
$now = time();
|
2022-01-06 18:44:56 +00:00
|
|
|
$expires = $now + $userEmailConfirmationTokenExpiry;
|
2012-03-26 03:44:57 +00:00
|
|
|
$expiration = wfTimestamp( TS_MW, $expires );
|
2008-03-25 22:03:00 +00:00
|
|
|
$this->load();
|
2012-03-21 10:27:34 +00:00
|
|
|
$token = MWCryptRand::generateHex( 32 );
|
2012-03-20 05:17:40 +00:00
|
|
|
$hash = md5( $token );
|
2008-03-25 22:03:00 +00:00
|
|
|
$this->mEmailToken = $hash;
|
2012-03-26 03:44:57 +00:00
|
|
|
$this->mEmailTokenExpires = $expiration;
|
2005-04-25 18:38:43 +00:00
|
|
|
return $token;
|
|
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
2013-03-07 16:27:38 +00:00
|
|
|
* Return a URL the user can use to confirm their email address.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $token Accepts the email confirmation token
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string New token URL
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2013-05-16 22:18:57 +00:00
|
|
|
protected function confirmationTokenUrl( $token ) {
|
2008-05-28 18:33:09 +00:00
|
|
|
return $this->getTokenUrl( 'ConfirmEmail', $token );
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2009-10-04 13:32:48 +00:00
|
|
|
|
2008-03-25 22:03:00 +00:00
|
|
|
/**
|
|
|
|
|
* Return a URL the user can use to invalidate their email address.
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $token Accepts the email confirmation token
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string New token URL
|
2008-03-25 22:03:00 +00:00
|
|
|
*/
|
2013-05-16 22:18:57 +00:00
|
|
|
protected function invalidationTokenUrl( $token ) {
|
2012-05-08 09:25:03 +00:00
|
|
|
return $this->getTokenUrl( 'InvalidateEmail', $token );
|
2008-05-28 18:33:09 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2008-05-28 18:33:09 +00:00
|
|
|
/**
|
|
|
|
|
* Internal function to format the e-mail validation/invalidation URLs.
|
2011-08-31 18:08:13 +00:00
|
|
|
* This uses a quickie hack to use the
|
2008-05-28 18:33:09 +00:00
|
|
|
* hardcoded English names of the Special: pages, for ASCII safety.
|
|
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @note Since these URLs get dropped directly into emails, using the
|
2021-11-22 13:35:17 +00:00
|
|
|
* short English names avoids really long URL-encoded links, which
|
2008-05-28 18:33:09 +00:00
|
|
|
* also sometimes can get corrupted in some browsers/mailers
|
2017-02-20 22:44:19 +00:00
|
|
|
* (T8957 with Gmail and Internet Explorer).
|
2008-08-05 13:42:02 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $page Special page
|
2017-12-28 15:06:10 +00:00
|
|
|
* @param string $token
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string Formatted URL
|
2008-05-28 18:33:09 +00:00
|
|
|
*/
|
|
|
|
|
protected function getTokenUrl( $page, $token ) {
|
2011-08-31 18:08:13 +00:00
|
|
|
// Hack to bypass localization of 'Special:'
|
|
|
|
|
$title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
|
2013-03-27 13:36:05 +00:00
|
|
|
return $title->getCanonicalURL();
|
2008-03-25 22:03:00 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
2008-04-15 09:04:45 +00:00
|
|
|
* Mark the e-mail address confirmed.
|
|
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @note Call saveSettings() after calling this function to commit the change.
|
2011-07-18 20:11:53 +00:00
|
|
|
*
|
2012-02-09 17:41:50 +00:00
|
|
|
* @return bool
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function confirmEmail() {
|
2013-03-28 22:29:22 +00:00
|
|
|
// Check if it's already confirmed, so we don't touch the database
|
|
|
|
|
// and fire the ConfirmEmailComplete hook on redundant confirmations.
|
|
|
|
|
if ( !$this->isEmailConfirmed() ) {
|
|
|
|
|
$this->setEmailAuthenticationTimestamp( wfTimestampNow() );
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onConfirmEmailComplete( $this );
|
2013-03-28 22:29:22 +00:00
|
|
|
}
|
2005-04-25 18:38:43 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-03-25 22:03:00 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Invalidate the user's e-mail confirmation, and unauthenticate the e-mail
|
|
|
|
|
* address if it was already confirmed.
|
2008-04-15 09:04:45 +00:00
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @note Call saveSettings() after calling this function to commit the change.
|
2012-02-10 15:37:33 +00:00
|
|
|
* @return bool Returns true
|
2008-03-25 22:03:00 +00:00
|
|
|
*/
|
2014-02-21 09:58:36 +00:00
|
|
|
public function invalidateEmail() {
|
2008-03-25 22:03:00 +00:00
|
|
|
$this->load();
|
|
|
|
|
$this->mEmailToken = null;
|
|
|
|
|
$this->mEmailTokenExpires = null;
|
2008-04-15 09:04:45 +00:00
|
|
|
$this->setEmailAuthenticationTimestamp( null );
|
2013-05-01 22:17:02 +00:00
|
|
|
$this->mEmail = '';
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onInvalidateEmailComplete( $this );
|
2008-03-25 22:03:00 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Set the e-mail authentication timestamp.
|
2019-12-28 22:16:36 +00:00
|
|
|
* @param string|null $timestamp TS_MW timestamp
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2014-02-21 09:58:36 +00:00
|
|
|
public function setEmailAuthenticationTimestamp( $timestamp ) {
|
2008-04-15 09:04:45 +00:00
|
|
|
$this->load();
|
|
|
|
|
$this->mEmailAuthenticated = $timestamp;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$this->getHookRunner()->onUserSetEmailAuthenticationTimestamp(
|
|
|
|
|
$this, $this->mEmailAuthenticated );
|
2008-04-15 09:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
|
|
|
|
* Is this user allowed to send e-mails within limits of current
|
|
|
|
|
* site configuration?
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function canSendEmail() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$enableEmail = MediaWikiServices::getInstance()->getMainConfig()->get( 'EnableEmail' );
|
|
|
|
|
$enableUserEmail = MediaWikiServices::getInstance()->getMainConfig()->get( 'EnableUserEmail' );
|
|
|
|
|
if ( !$enableEmail || !$enableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
|
2008-10-17 22:20:07 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2021-03-07 10:56:14 +00:00
|
|
|
$hookErr = $this->isEmailConfirmed();
|
|
|
|
|
$this->getHookRunner()->onUserCanSendEmail( $this, $hookErr );
|
|
|
|
|
return $hookErr;
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
|
|
|
|
* Is this user allowed to receive e-mails within limits of current
|
|
|
|
|
* site configuration?
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function canReceiveEmail() {
|
2008-01-29 00:29:38 +00:00
|
|
|
return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2005-07-24 06:55:45 +00:00
|
|
|
|
2005-04-25 18:38:43 +00:00
|
|
|
/**
|
|
|
|
|
* Is this user's e-mail address valid-looking and confirmed within
|
|
|
|
|
* limits of the current site configuration?
|
|
|
|
|
*
|
2008-08-05 13:42:02 +00:00
|
|
|
* @note If $wgEmailAuthentication is on, this may require the user to have
|
2005-04-25 18:38:43 +00:00
|
|
|
* confirmed their address by returning a code or using a password
|
|
|
|
|
* sent to the address from the wiki.
|
|
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2005-04-25 18:38:43 +00:00
|
|
|
*/
|
2021-03-26 01:50:30 +00:00
|
|
|
public function isEmailConfirmed(): bool {
|
2022-01-06 18:44:56 +00:00
|
|
|
$emailAuthentication = MediaWikiServices::getInstance()->getMainConfig()->get( 'EmailAuthentication' );
|
2006-10-14 06:58:19 +00:00
|
|
|
$this->load();
|
2016-12-22 10:31:10 +00:00
|
|
|
// Avoid PHP 7.1 warning of passing $this by reference
|
|
|
|
|
$user = $this;
|
2006-05-02 20:05:25 +00:00
|
|
|
$confirmed = true;
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
if ( $this->getHookRunner()->onEmailConfirmed( $user, $confirmed ) ) {
|
2013-04-20 22:49:30 +00:00
|
|
|
if ( $this->isAnon() ) {
|
2006-05-02 20:05:25 +00:00
|
|
|
return false;
|
2011-06-26 19:16:04 +00:00
|
|
|
}
|
2021-01-12 21:47:48 +00:00
|
|
|
if ( !Sanitizer::validateEmail( $this->getEmail() ) ) {
|
2006-05-02 20:05:25 +00:00
|
|
|
return false;
|
2011-06-26 19:16:04 +00:00
|
|
|
}
|
2022-01-06 18:44:56 +00:00
|
|
|
if ( $emailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
|
2006-05-02 20:05:25 +00:00
|
|
|
return false;
|
2011-06-26 19:16:04 +00:00
|
|
|
}
|
2006-05-02 20:05:25 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
return $confirmed;
|
2005-04-25 18:38:43 +00:00
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2006-12-14 00:31:16 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Check whether there is an outstanding request for e-mail confirmation.
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return bool
|
2006-12-14 00:31:16 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public function isEmailConfirmationPending() {
|
2022-01-06 18:44:56 +00:00
|
|
|
$emailAuthentication = MediaWikiServices::getInstance()->getMainConfig()->get( 'EmailAuthentication' );
|
|
|
|
|
return $emailAuthentication &&
|
2006-12-14 00:31:16 +00:00
|
|
|
!$this->isEmailConfirmed() &&
|
|
|
|
|
$this->mEmailToken &&
|
|
|
|
|
$this->mEmailTokenExpires > wfTimestamp();
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2007-06-27 14:32:31 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the timestamp of account creation.
|
2007-06-27 14:32:31 +00:00
|
|
|
*
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string|bool|null Timestamp of account creation, false for
|
|
|
|
|
* non-existent/anonymous user accounts, or null if existing account
|
|
|
|
|
* but information is not in database.
|
2007-06-27 14:32:31 +00:00
|
|
|
*/
|
|
|
|
|
public function getRegistration() {
|
2011-04-30 14:08:12 +00:00
|
|
|
if ( $this->isAnon() ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$this->load();
|
|
|
|
|
return $this->mRegistration;
|
2007-06-27 14:32:31 +00:00
|
|
|
}
|
2009-06-18 02:50:16 +00:00
|
|
|
|
2005-06-09 09:49:10 +00:00
|
|
|
/**
|
2008-08-05 13:42:02 +00:00
|
|
|
* Get the permissions associated with a given list of groups
|
|
|
|
|
*
|
2021-05-26 05:12:16 +00:00
|
|
|
* @deprecated since 1.34, use GroupPermissionsLookup::getGroupPermissions() instead in 1.36+,
|
|
|
|
|
* or PermissionManager::getGroupPermisions() in 1.34 and 1.35
|
2019-04-09 06:58:04 +00:00
|
|
|
*
|
2020-10-28 10:01:33 +00:00
|
|
|
* @param string[] $groups internal group names
|
|
|
|
|
* @return string[] permission key names for given groups combined
|
2005-06-09 09:49:10 +00:00
|
|
|
*/
|
2011-12-12 06:03:01 +00:00
|
|
|
public static function getGroupPermissions( $groups ) {
|
2021-05-26 05:12:16 +00:00
|
|
|
return MediaWikiServices::getInstance()->getGroupPermissionsLookup()->getGroupPermissions( $groups );
|
2005-06-09 09:49:10 +00:00
|
|
|
}
|
2011-07-18 19:56:16 +00:00
|
|
|
|
2008-08-04 05:14:33 +00:00
|
|
|
/**
|
|
|
|
|
* Get all the groups who have a given permission
|
2009-06-18 02:50:16 +00:00
|
|
|
*
|
2021-05-26 05:12:16 +00:00
|
|
|
* @deprecated since 1.34, use GroupPermissionsLookup::getGroupsWithPermission() instead in 1.36+,
|
|
|
|
|
* or PermissionManager::getGroupsWithPermission() in 1.34 and 1.35
|
2019-04-09 06:58:04 +00:00
|
|
|
*
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $role Role to check
|
2020-10-28 10:01:33 +00:00
|
|
|
* @return string[] internal group names with the given permission
|
2008-08-04 05:14:33 +00:00
|
|
|
*/
|
2011-12-12 06:03:01 +00:00
|
|
|
public static function getGroupsWithPermission( $role ) {
|
2021-05-26 05:12:16 +00:00
|
|
|
return MediaWikiServices::getInstance()->getGroupPermissionsLookup()->getGroupsWithPermission( $role );
|
2008-08-04 05:14:33 +00:00
|
|
|
}
|
2005-06-09 09:49:10 +00:00
|
|
|
|
2012-10-04 16:17:46 +00:00
|
|
|
/**
|
|
|
|
|
* Check, if the given group has the given permission
|
|
|
|
|
*
|
2013-07-12 15:06:41 +00:00
|
|
|
* If you're wanting to check whether all users have a permission, use
|
2019-10-30 02:10:20 +00:00
|
|
|
* PermissionManager::isEveryoneAllowed() instead. That properly checks if it's revoked
|
2013-07-12 15:06:41 +00:00
|
|
|
* from anyone.
|
|
|
|
|
*
|
2021-05-26 05:12:16 +00:00
|
|
|
* @deprecated since 1.34, use GroupPermissionsLookup::groupHasPermission() instead in 1.36+,
|
|
|
|
|
* or PermissionManager::groupHasPermission() in 1.34 and 1.35
|
2019-04-09 06:58:04 +00:00
|
|
|
*
|
2013-04-11 02:18:43 +00:00
|
|
|
* @since 1.21
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $group Group to check
|
|
|
|
|
* @param string $role Role to check
|
2012-10-04 16:17:46 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public static function groupHasPermission( $group, $role ) {
|
2021-05-26 05:12:16 +00:00
|
|
|
return MediaWikiServices::getInstance()->getGroupPermissionsLookup()
|
2019-04-09 06:58:04 +00:00
|
|
|
->groupHasPermission( $group, $role );
|
2012-10-04 16:17:46 +00:00
|
|
|
}
|
|
|
|
|
|
2005-06-09 09:49:10 +00:00
|
|
|
/**
|
|
|
|
|
* Return the set of defined explicit groups.
|
2008-05-05 18:04:48 +00:00
|
|
|
* The implicit groups (by default *, 'user' and 'autoconfirmed')
|
2008-08-05 13:42:02 +00:00
|
|
|
* are not included, as they are defined automatically, not in the database.
|
2019-10-24 03:14:31 +00:00
|
|
|
* @deprecated since 1.35, use UserGroupManager::listAllGroups instead
|
2020-10-28 10:01:33 +00:00
|
|
|
* @return string[] internal group names
|
2005-06-09 09:49:10 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public static function getAllGroups() {
|
2019-10-24 03:14:31 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
|
|
|
|
->listAllGroups();
|
2007-08-09 16:36:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-24 03:14:31 +00:00
|
|
|
* @deprecated since 1.35, use UserGroupManager::listAllImplicitGroups() instead
|
2020-10-28 10:01:33 +00:00
|
|
|
* @return string[] internal group names
|
2007-08-09 16:36:15 +00:00
|
|
|
*/
|
|
|
|
|
public static function getImplicitGroups() {
|
2019-10-24 03:14:31 +00:00
|
|
|
return MediaWikiServices::getInstance()
|
|
|
|
|
->getUserGroupManager()
|
|
|
|
|
->listAllImplicitGroups();
|
2005-06-09 09:49:10 +00:00
|
|
|
}
|
2006-08-12 09:24:18 +00:00
|
|
|
|
2015-04-13 23:02:16 +00:00
|
|
|
/**
|
2018-10-22 22:58:02 +00:00
|
|
|
* Schedule a deferred update to update the user's edit count
|
2021-04-24 04:54:48 +00:00
|
|
|
* @deprecated since 1.37
|
2015-04-13 23:02:16 +00:00
|
|
|
*/
|
|
|
|
|
public function incEditCount() {
|
2021-04-24 04:54:48 +00:00
|
|
|
MediaWikiServices::getInstance()->getUserEditTracker()->incrementUserEditCount( $this );
|
2015-04-13 23:02:16 +00:00
|
|
|
}
|
|
|
|
|
|
2008-08-05 13:42:02 +00:00
|
|
|
/**
|
|
|
|
|
* Get the description of a given right
|
|
|
|
|
*
|
2016-11-29 22:36:21 +00:00
|
|
|
* @since 1.29
|
2013-03-11 17:15:01 +00:00
|
|
|
* @param string $right Right to query
|
2013-06-03 22:02:10 +00:00
|
|
|
* @return string Localized description of the right
|
2008-08-05 13:42:02 +00:00
|
|
|
*/
|
2011-07-18 19:56:16 +00:00
|
|
|
public static function getRightDescription( $right ) {
|
2008-04-24 08:58:39 +00:00
|
|
|
$key = "right-$right";
|
2011-05-24 17:28:21 +00:00
|
|
|
$msg = wfMessage( $key );
|
2016-11-29 22:36:21 +00:00
|
|
|
return $msg->isDisabled() ? $right : $msg->text();
|
2008-04-24 08:58:39 +00:00
|
|
|
}
|
2008-06-05 12:58:02 +00:00
|
|
|
|
2017-10-06 17:03:55 +00:00
|
|
|
/**
|
|
|
|
|
* Return the tables, fields, and join conditions to be selected to create
|
|
|
|
|
* a new user object.
|
|
|
|
|
* @since 1.31
|
|
|
|
|
* @return array With three keys:
|
|
|
|
|
* - tables: (string[]) to include in the `$table` to `IDatabase->select()`
|
|
|
|
|
* - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
|
|
|
|
|
* - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
|
|
|
|
|
*/
|
|
|
|
|
public static function getQueryInfo() {
|
2017-09-12 17:12:29 +00:00
|
|
|
$ret = [
|
2019-07-23 17:40:52 +00:00
|
|
|
'tables' => [ 'user', 'user_actor' => 'actor' ],
|
2017-10-06 17:03:55 +00:00
|
|
|
'fields' => [
|
|
|
|
|
'user_id',
|
|
|
|
|
'user_name',
|
|
|
|
|
'user_real_name',
|
|
|
|
|
'user_email',
|
|
|
|
|
'user_touched',
|
|
|
|
|
'user_token',
|
|
|
|
|
'user_email_authenticated',
|
|
|
|
|
'user_email_token',
|
|
|
|
|
'user_email_token_expires',
|
|
|
|
|
'user_registration',
|
|
|
|
|
'user_editcount',
|
2019-07-23 17:40:52 +00:00
|
|
|
'user_actor.actor_id',
|
|
|
|
|
],
|
|
|
|
|
'joins' => [
|
|
|
|
|
'user_actor' => [ 'JOIN', 'user_actor.actor_user = user_id' ],
|
2017-10-06 17:03:55 +00:00
|
|
|
],
|
|
|
|
|
];
|
2018-09-18 18:21:20 +00:00
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
return $ret;
|
2017-10-06 17:03:55 +00:00
|
|
|
}
|
|
|
|
|
|
2013-06-13 18:02:55 +00:00
|
|
|
/**
|
|
|
|
|
* Factory function for fatal permission-denied errors
|
|
|
|
|
*
|
|
|
|
|
* @since 1.22
|
|
|
|
|
* @param string $permission User right required
|
|
|
|
|
* @return Status
|
|
|
|
|
*/
|
2020-05-17 23:02:18 +00:00
|
|
|
public static function newFatalPermissionDeniedStatus( $permission ) {
|
2013-06-13 18:02:55 +00:00
|
|
|
global $wgLang;
|
|
|
|
|
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
$groups = [];
|
2019-08-22 02:22:26 +00:00
|
|
|
foreach ( MediaWikiServices::getInstance()
|
2021-05-26 05:12:16 +00:00
|
|
|
->getGroupPermissionsLookup()
|
2019-10-24 03:14:31 +00:00
|
|
|
->getGroupsWithPermission( $permission ) as $group ) {
|
User group memberships that expire
This patch adds an ug_expiry column to the user_groups table, a timestamp
giving a date when the user group expires. A new UserGroupMembership class,
based on the Block class, manages entries in this table.
When the expiry date passes, the row in user_groups is ignored, and will
eventually be purged from the DB when UserGroupMembership::insert is next
called. Old, expired user group memberships are not kept; instead, the log
entries are available to find the history of these memberships, similar
to the way it has always worked for blocks and protections.
Anyone getting user group info through the User object will get correct
information. However, code that reads the user_groups table directly will
now need to skip over rows with ug_expiry < wfTimestampNow(). See
UsersPager for an example of how to do this.
NULL is used to represent infinite (no) expiry, rather than a string
'infinity' or similar (except in the API). This allows existing user group
assignments and log entries, which are all infinite in duration, to be
treated the same as new, infinite-length memberships, without special
casing everything.
The whole thing is behind the temporary feature flag
$wgDisableUserGroupExpiry, in accordance with the WMF schema change policy.
The opportunity has been taken to refactor some static user-group-related
functions out of User into UserGroupMembership, and also to add a primary
key (ug_user, ug_group) to the user_groups table.
There are a few breaking changes:
- UserRightsProxy-like objects are now required to have a
getGroupMemberships() function.
- $user->mGroups (on a User object) is no longer present.
- Some protected functions in UsersPager are altered or removed.
- The UsersPagerDoBatchLookups hook (unused in any Wikimedia Git-hosted
extension) has a change of parameter.
Bug: T12493
Depends-On: Ia9616e1e35184fed9058d2d39afbe1038f56d7fa
Depends-On: I86eb1d5619347ce54a5f33a591417742ebe5d6f8
Change-Id: I93c955dc7a970f78e32aa503c01c67da30971d1a
2017-01-12 06:07:56 +00:00
|
|
|
$groups[] = UserGroupMembership::getLink( $group, RequestContext::getMain(), 'wiki' );
|
|
|
|
|
}
|
2013-06-13 18:02:55 +00:00
|
|
|
|
|
|
|
|
if ( $groups ) {
|
|
|
|
|
return Status::newFatal( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
|
|
|
|
|
}
|
2019-02-07 03:28:29 +00:00
|
|
|
|
|
|
|
|
return Status::newFatal( 'badaccess-group0' );
|
2013-06-13 18:02:55 +00:00
|
|
|
}
|
2014-12-11 08:59:28 +00:00
|
|
|
|
2015-12-13 04:35:22 +00:00
|
|
|
/**
|
2021-09-01 21:04:40 +00:00
|
|
|
* Get a new instance of this user that was loaded from the primary DB via a locking read
|
2015-12-13 04:35:22 +00:00
|
|
|
*
|
|
|
|
|
* Use this instead of the main context User when updating that user. This avoids races
|
2021-09-01 21:04:40 +00:00
|
|
|
* where that user was loaded from a replica DB or even the primary DB but without proper locks.
|
2015-12-13 04:35:22 +00:00
|
|
|
*
|
|
|
|
|
* @return User|null Returns null if the user was not found in the DB
|
|
|
|
|
* @since 1.27
|
|
|
|
|
*/
|
|
|
|
|
public function getInstanceForUpdate() {
|
|
|
|
|
if ( !$this->getId() ) {
|
|
|
|
|
return null; // anon
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = self::newFromId( $this->getId() );
|
2021-03-10 19:40:33 +00:00
|
|
|
if ( !$user->loadFromId( IDBAccessObject::READ_EXCLUSIVE ) ) {
|
2015-12-13 04:35:22 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $user;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-11 08:59:28 +00:00
|
|
|
/**
|
|
|
|
|
* Checks if two user objects point to the same user.
|
|
|
|
|
*
|
2018-05-08 14:31:03 +00:00
|
|
|
* @since 1.25 ; takes a UserIdentity instead of a User since 1.32
|
2021-05-27 16:56:43 +00:00
|
|
|
* @param UserIdentity|null $user
|
2014-12-11 08:59:28 +00:00
|
|
|
* @return bool
|
|
|
|
|
*/
|
2021-07-22 03:11:47 +00:00
|
|
|
public function equals( ?UserIdentity $user ): bool {
|
2021-05-27 16:56:43 +00:00
|
|
|
if ( !$user ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-08 14:31:03 +00:00
|
|
|
// XXX it's not clear whether central ID providers are supposed to obey this
|
2014-12-11 08:59:28 +00:00
|
|
|
return $this->getName() === $user->getName();
|
|
|
|
|
}
|
2019-03-07 20:02:07 +00:00
|
|
|
|
2021-01-07 20:57:19 +00:00
|
|
|
/**
|
2021-04-13 18:38:36 +00:00
|
|
|
* @note This is only here for compatibility with the Authority interface.
|
|
|
|
|
* @since 1.36
|
|
|
|
|
* @return UserIdentity $this
|
2021-01-07 20:57:19 +00:00
|
|
|
*/
|
2021-03-04 19:45:28 +00:00
|
|
|
public function getUser(): UserIdentity {
|
2021-01-07 20:57:19 +00:00
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-04-13 18:38:36 +00:00
|
|
|
* @since 1.36
|
2021-01-07 20:57:19 +00:00
|
|
|
* @param string $action
|
|
|
|
|
* @param PageIdentity $target
|
|
|
|
|
* @param PermissionStatus|null $status
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function probablyCan( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->probablyCan( $action, $target, $status );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-04-13 18:38:36 +00:00
|
|
|
* @since 1.36
|
2021-01-07 20:57:19 +00:00
|
|
|
* @param string $action
|
|
|
|
|
* @param PageIdentity $target
|
|
|
|
|
* @param PermissionStatus|null $status
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function definitelyCan( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->definitelyCan( $action, $target, $status );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-04-13 18:38:36 +00:00
|
|
|
* @since 1.36
|
2021-01-07 20:57:19 +00:00
|
|
|
* @param string $action
|
|
|
|
|
* @param PageIdentity $target
|
|
|
|
|
* @param PermissionStatus|null $status
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function authorizeRead( string $action, PageIdentity $target, PermissionStatus $status = null
|
|
|
|
|
): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->authorizeRead( $action, $target, $status );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-04-13 18:38:36 +00:00
|
|
|
* @since 1.36
|
2021-01-07 20:57:19 +00:00
|
|
|
* @param string $action
|
|
|
|
|
* @param PageIdentity $target
|
|
|
|
|
* @param PermissionStatus|null $status
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function authorizeWrite( string $action, PageIdentity $target, PermissionStatus $status = null ): bool {
|
|
|
|
|
return $this->getThisAsAuthority()->authorizeWrite( $action, $target, $status );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the Authority of this User if it's the main request context user.
|
|
|
|
|
* This is intended to exist only for the period of transition to Authority.
|
|
|
|
|
* @return Authority
|
|
|
|
|
*/
|
|
|
|
|
private function getThisAsAuthority(): Authority {
|
|
|
|
|
if ( !$this->mThisAsAuthority ) {
|
|
|
|
|
// TODO: For users that are not User::isGlobalSessionUser,
|
|
|
|
|
// creating a UserAuthority here is incorrect, since it depends
|
|
|
|
|
// on global WebRequest, but that is what we've used to do before Authority.
|
|
|
|
|
// When PermissionManager is refactored into Authority, we need
|
|
|
|
|
// to provide base implementation, based on just user groups/rights,
|
|
|
|
|
// and use it here.
|
|
|
|
|
$this->mThisAsAuthority = new UserAuthority(
|
|
|
|
|
$this,
|
|
|
|
|
MediaWikiServices::getInstance()->getPermissionManager()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return $this->mThisAsAuthority;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check whether this is the global session user.
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
private function isGlobalSessionUser(): bool {
|
|
|
|
|
// The session user is set up towards the end of Setup.php. Until then,
|
|
|
|
|
// assume it's a logged-out user.
|
|
|
|
|
$sessionUser = RequestContext::getMain()->getUser();
|
|
|
|
|
$globalUserName = $sessionUser->isSafeToLoad()
|
|
|
|
|
? $sessionUser->getName()
|
|
|
|
|
: IPUtils::sanitizeIP( $sessionUser->getRequest()->getIP() );
|
|
|
|
|
|
|
|
|
|
return $this->getName() === $globalUserName;
|
|
|
|
|
}
|
2009-04-20 14:35:35 +00:00
|
|
|
}
|