Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like User often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use User, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 48 | class User implements IDBAccessObject { |
||
| 49 | /** |
||
| 50 | * @const int Number of characters in user_token field. |
||
| 51 | */ |
||
| 52 | const TOKEN_LENGTH = 32; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * @const string An invalid value for user_token |
||
| 56 | */ |
||
| 57 | const INVALID_TOKEN = '*** INVALID ***'; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * Global constant made accessible as class constants so that autoloader |
||
| 61 | * magic can be used. |
||
| 62 | * @deprecated since 1.27, use \MediaWiki\Session\Token::SUFFIX |
||
| 63 | */ |
||
| 64 | const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX; |
||
| 65 | |||
| 66 | /** |
||
| 67 | * @const int Serialized record version. |
||
| 68 | */ |
||
| 69 | const VERSION = 10; |
||
| 70 | |||
| 71 | /** |
||
| 72 | * Exclude user options that are set to their default value. |
||
| 73 | * @since 1.25 |
||
| 74 | */ |
||
| 75 | const GETOPTIONS_EXCLUDE_DEFAULTS = 1; |
||
| 76 | |||
| 77 | /** |
||
| 78 | * @since 1.27 |
||
| 79 | */ |
||
| 80 | const CHECK_USER_RIGHTS = true; |
||
| 81 | |||
| 82 | /** |
||
| 83 | * @since 1.27 |
||
| 84 | */ |
||
| 85 | const IGNORE_USER_RIGHTS = false; |
||
| 86 | |||
| 87 | /** |
||
| 88 | * Array of Strings List of member variables which are saved to the |
||
| 89 | * shared cache (memcached). Any operation which changes the |
||
| 90 | * corresponding database fields must call a cache-clearing function. |
||
| 91 | * @showinitializer |
||
| 92 | */ |
||
| 93 | protected static $mCacheVars = [ |
||
| 94 | // user table |
||
| 95 | 'mId', |
||
| 96 | 'mName', |
||
| 97 | 'mRealName', |
||
| 98 | 'mEmail', |
||
| 99 | 'mTouched', |
||
| 100 | 'mToken', |
||
| 101 | 'mEmailAuthenticated', |
||
| 102 | 'mEmailToken', |
||
| 103 | 'mEmailTokenExpires', |
||
| 104 | 'mRegistration', |
||
| 105 | 'mEditCount', |
||
| 106 | // user_groups table |
||
| 107 | 'mGroups', |
||
| 108 | // user_properties table |
||
| 109 | 'mOptionOverrides', |
||
| 110 | ]; |
||
| 111 | |||
| 112 | /** |
||
| 113 | * Array of Strings Core rights. |
||
| 114 | * Each of these should have a corresponding message of the form |
||
| 115 | * "right-$right". |
||
| 116 | * @showinitializer |
||
| 117 | */ |
||
| 118 | protected static $mCoreRights = [ |
||
| 119 | 'apihighlimits', |
||
| 120 | 'applychangetags', |
||
| 121 | 'autoconfirmed', |
||
| 122 | 'autocreateaccount', |
||
| 123 | 'autopatrol', |
||
| 124 | 'bigdelete', |
||
| 125 | 'block', |
||
| 126 | 'blockemail', |
||
| 127 | 'bot', |
||
| 128 | 'browsearchive', |
||
| 129 | 'changetags', |
||
| 130 | 'createaccount', |
||
| 131 | 'createpage', |
||
| 132 | 'createtalk', |
||
| 133 | 'delete', |
||
| 134 | 'deletechangetags', |
||
| 135 | 'deletedhistory', |
||
| 136 | 'deletedtext', |
||
| 137 | 'deletelogentry', |
||
| 138 | 'deleterevision', |
||
| 139 | 'edit', |
||
| 140 | 'editcontentmodel', |
||
| 141 | 'editinterface', |
||
| 142 | 'editprotected', |
||
| 143 | 'editmyoptions', |
||
| 144 | 'editmyprivateinfo', |
||
| 145 | 'editmyusercss', |
||
| 146 | 'editmyuserjs', |
||
| 147 | 'editmywatchlist', |
||
| 148 | 'editsemiprotected', |
||
| 149 | 'editusercssjs', # deprecated |
||
| 150 | 'editusercss', |
||
| 151 | 'edituserjs', |
||
| 152 | 'hideuser', |
||
| 153 | 'import', |
||
| 154 | 'importupload', |
||
| 155 | 'ipblock-exempt', |
||
| 156 | 'managechangetags', |
||
| 157 | 'markbotedits', |
||
| 158 | 'mergehistory', |
||
| 159 | 'minoredit', |
||
| 160 | 'move', |
||
| 161 | 'movefile', |
||
| 162 | 'move-categorypages', |
||
| 163 | 'move-rootuserpages', |
||
| 164 | 'move-subpages', |
||
| 165 | 'nominornewtalk', |
||
| 166 | 'noratelimit', |
||
| 167 | 'override-export-depth', |
||
| 168 | 'pagelang', |
||
| 169 | 'passwordreset', |
||
| 170 | 'patrol', |
||
| 171 | 'patrolmarks', |
||
| 172 | 'protect', |
||
| 173 | 'purge', |
||
| 174 | 'read', |
||
| 175 | 'reupload', |
||
| 176 | 'reupload-own', |
||
| 177 | 'reupload-shared', |
||
| 178 | 'rollback', |
||
| 179 | 'sendemail', |
||
| 180 | 'siteadmin', |
||
| 181 | 'suppressionlog', |
||
| 182 | 'suppressredirect', |
||
| 183 | 'suppressrevision', |
||
| 184 | 'unblockself', |
||
| 185 | 'undelete', |
||
| 186 | 'unwatchedpages', |
||
| 187 | 'upload', |
||
| 188 | 'upload_by_url', |
||
| 189 | 'userrights', |
||
| 190 | 'userrights-interwiki', |
||
| 191 | 'viewmyprivateinfo', |
||
| 192 | 'viewmywatchlist', |
||
| 193 | 'viewsuppressed', |
||
| 194 | 'writeapi', |
||
| 195 | ]; |
||
| 196 | |||
| 197 | /** |
||
| 198 | * String Cached results of getAllRights() |
||
| 199 | */ |
||
| 200 | protected static $mAllRights = false; |
||
| 201 | |||
| 202 | /** Cache variables */ |
||
| 203 | // @{ |
||
| 204 | /** @var int */ |
||
| 205 | public $mId; |
||
| 206 | /** @var string */ |
||
| 207 | public $mName; |
||
| 208 | /** @var string */ |
||
| 209 | public $mRealName; |
||
| 210 | |||
| 211 | /** @var string */ |
||
| 212 | public $mEmail; |
||
| 213 | /** @var string TS_MW timestamp from the DB */ |
||
| 214 | public $mTouched; |
||
| 215 | /** @var string TS_MW timestamp from cache */ |
||
| 216 | protected $mQuickTouched; |
||
| 217 | /** @var string */ |
||
| 218 | protected $mToken; |
||
| 219 | /** @var string */ |
||
| 220 | public $mEmailAuthenticated; |
||
| 221 | /** @var string */ |
||
| 222 | protected $mEmailToken; |
||
| 223 | /** @var string */ |
||
| 224 | protected $mEmailTokenExpires; |
||
| 225 | /** @var string */ |
||
| 226 | protected $mRegistration; |
||
| 227 | /** @var int */ |
||
| 228 | protected $mEditCount; |
||
| 229 | /** @var array */ |
||
| 230 | public $mGroups; |
||
| 231 | /** @var array */ |
||
| 232 | protected $mOptionOverrides; |
||
| 233 | // @} |
||
| 234 | |||
| 235 | /** |
||
| 236 | * Bool Whether the cache variables have been loaded. |
||
| 237 | */ |
||
| 238 | // @{ |
||
| 239 | public $mOptionsLoaded; |
||
| 240 | |||
| 241 | /** |
||
| 242 | * Array with already loaded items or true if all items have been loaded. |
||
| 243 | */ |
||
| 244 | protected $mLoadedItems = []; |
||
| 245 | // @} |
||
| 246 | |||
| 247 | /** |
||
| 248 | * String Initialization data source if mLoadedItems!==true. May be one of: |
||
| 249 | * - 'defaults' anonymous user initialised from class defaults |
||
| 250 | * - 'name' initialise from mName |
||
| 251 | * - 'id' initialise from mId |
||
| 252 | * - 'session' log in from session if possible |
||
| 253 | * |
||
| 254 | * Use the User::newFrom*() family of functions to set this. |
||
| 255 | */ |
||
| 256 | public $mFrom; |
||
| 257 | |||
| 258 | /** |
||
| 259 | * Lazy-initialized variables, invalidated with clearInstanceCache |
||
| 260 | */ |
||
| 261 | protected $mNewtalk; |
||
| 262 | /** @var string */ |
||
| 263 | protected $mDatePreference; |
||
| 264 | /** @var string */ |
||
| 265 | public $mBlockedby; |
||
| 266 | /** @var string */ |
||
| 267 | protected $mHash; |
||
| 268 | /** @var array */ |
||
| 269 | public $mRights; |
||
| 270 | /** @var string */ |
||
| 271 | protected $mBlockreason; |
||
| 272 | /** @var array */ |
||
| 273 | protected $mEffectiveGroups; |
||
| 274 | /** @var array */ |
||
| 275 | protected $mImplicitGroups; |
||
| 276 | /** @var array */ |
||
| 277 | protected $mFormerGroups; |
||
| 278 | /** @var Block */ |
||
| 279 | protected $mGlobalBlock; |
||
| 280 | /** @var bool */ |
||
| 281 | protected $mLocked; |
||
| 282 | /** @var bool */ |
||
| 283 | public $mHideName; |
||
| 284 | /** @var array */ |
||
| 285 | public $mOptions; |
||
| 286 | |||
| 287 | /** |
||
| 288 | * @var WebRequest |
||
| 289 | */ |
||
| 290 | private $mRequest; |
||
| 291 | |||
| 292 | /** @var Block */ |
||
| 293 | public $mBlock; |
||
| 294 | |||
| 295 | /** @var bool */ |
||
| 296 | protected $mAllowUsertalk; |
||
| 297 | |||
| 298 | /** @var Block */ |
||
| 299 | private $mBlockedFromCreateAccount = false; |
||
| 300 | |||
| 301 | /** @var integer User::READ_* constant bitfield used to load data */ |
||
| 302 | protected $queryFlagsUsed = self::READ_NORMAL; |
||
| 303 | |||
| 304 | public static $idCacheByName = []; |
||
| 305 | |||
| 306 | /** |
||
| 307 | * Lightweight constructor for an anonymous user. |
||
| 308 | * Use the User::newFrom* factory functions for other kinds of users. |
||
| 309 | * |
||
| 310 | * @see newFromName() |
||
| 311 | * @see newFromId() |
||
| 312 | * @see newFromConfirmationCode() |
||
| 313 | * @see newFromSession() |
||
| 314 | * @see newFromRow() |
||
| 315 | */ |
||
| 316 | public function __construct() { |
||
| 317 | $this->clearInstanceCache( 'defaults' ); |
||
| 318 | } |
||
| 319 | |||
| 320 | /** |
||
| 321 | * @return string |
||
| 322 | */ |
||
| 323 | public function __toString() { |
||
| 324 | return (string)$this->getName(); |
||
| 325 | } |
||
| 326 | |||
| 327 | /** |
||
| 328 | * Test if it's safe to load this User object. |
||
| 329 | * |
||
| 330 | * You should typically check this before using $wgUser or |
||
| 331 | * RequestContext::getUser in a method that might be called before the |
||
| 332 | * system has been fully initialized. If the object is unsafe, you should |
||
| 333 | * use an anonymous user: |
||
| 334 | * \code |
||
| 335 | * $user = $wgUser->isSafeToLoad() ? $wgUser : new User; |
||
| 336 | * \endcode |
||
| 337 | * |
||
| 338 | * @since 1.27 |
||
| 339 | * @return bool |
||
| 340 | */ |
||
| 341 | public function isSafeToLoad() { |
||
| 342 | global $wgFullyInitialised; |
||
| 343 | |||
| 344 | // The user is safe to load if: |
||
| 345 | // * MW_NO_SESSION is undefined AND $wgFullyInitialised is true (safe to use session data) |
||
| 346 | // * mLoadedItems === true (already loaded) |
||
| 347 | // * mFrom !== 'session' (sessions not involved at all) |
||
| 348 | |||
| 349 | return ( !defined( 'MW_NO_SESSION' ) && $wgFullyInitialised ) || |
||
| 350 | $this->mLoadedItems === true || $this->mFrom !== 'session'; |
||
| 351 | } |
||
| 352 | |||
| 353 | /** |
||
| 354 | * Load the user table data for this object from the source given by mFrom. |
||
| 355 | * |
||
| 356 | * @param integer $flags User::READ_* constant bitfield |
||
| 357 | */ |
||
| 358 | public function load( $flags = self::READ_NORMAL ) { |
||
| 359 | global $wgFullyInitialised; |
||
| 360 | |||
| 361 | if ( $this->mLoadedItems === true ) { |
||
| 362 | return; |
||
| 363 | } |
||
| 364 | |||
| 365 | // Set it now to avoid infinite recursion in accessors |
||
| 366 | $oldLoadedItems = $this->mLoadedItems; |
||
| 367 | $this->mLoadedItems = true; |
||
| 368 | $this->queryFlagsUsed = $flags; |
||
| 369 | |||
| 370 | // If this is called too early, things are likely to break. |
||
| 371 | if ( !$wgFullyInitialised && $this->mFrom === 'session' ) { |
||
| 372 | \MediaWiki\Logger\LoggerFactory::getInstance( 'session' ) |
||
| 373 | ->warning( 'User::loadFromSession called before the end of Setup.php', [ |
||
| 374 | 'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ), |
||
| 375 | ] ); |
||
| 376 | $this->loadDefaults(); |
||
| 377 | $this->mLoadedItems = $oldLoadedItems; |
||
| 378 | return; |
||
| 379 | } |
||
| 380 | |||
| 381 | switch ( $this->mFrom ) { |
||
| 382 | case 'defaults': |
||
| 383 | $this->loadDefaults(); |
||
| 384 | break; |
||
| 385 | case 'name': |
||
| 386 | // Make sure this thread sees its own changes |
||
| 387 | if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) { |
||
| 388 | $flags |= self::READ_LATEST; |
||
| 389 | $this->queryFlagsUsed = $flags; |
||
| 390 | } |
||
| 391 | |||
| 392 | $this->mId = self::idFromName( $this->mName, $flags ); |
||
| 393 | if ( !$this->mId ) { |
||
| 394 | // Nonexistent user placeholder object |
||
| 395 | $this->loadDefaults( $this->mName ); |
||
| 396 | } else { |
||
| 397 | $this->loadFromId( $flags ); |
||
| 398 | } |
||
| 399 | break; |
||
| 400 | case 'id': |
||
| 401 | $this->loadFromId( $flags ); |
||
| 402 | break; |
||
| 403 | case 'session': |
||
| 404 | if ( !$this->loadFromSession() ) { |
||
| 405 | // Loading from session failed. Load defaults. |
||
| 406 | $this->loadDefaults(); |
||
| 407 | } |
||
| 408 | Hooks::run( 'UserLoadAfterLoadFromSession', [ $this ] ); |
||
| 409 | break; |
||
| 410 | default: |
||
| 411 | throw new UnexpectedValueException( |
||
| 412 | "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" ); |
||
| 413 | } |
||
| 414 | } |
||
| 415 | |||
| 416 | /** |
||
| 417 | * Load user table data, given mId has already been set. |
||
| 418 | * @param integer $flags User::READ_* constant bitfield |
||
| 419 | * @return bool False if the ID does not exist, true otherwise |
||
| 420 | */ |
||
| 421 | public function loadFromId( $flags = self::READ_NORMAL ) { |
||
| 422 | if ( $this->mId == 0 ) { |
||
| 423 | // Anonymous users are not in the database (don't need cache) |
||
| 424 | $this->loadDefaults(); |
||
| 425 | return false; |
||
| 426 | } |
||
| 427 | |||
| 428 | // Try cache (unless this needs data from the master DB). |
||
| 429 | // NOTE: if this thread called saveSettings(), the cache was cleared. |
||
| 430 | $latest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST ); |
||
| 431 | if ( $latest ) { |
||
| 432 | if ( !$this->loadFromDatabase( $flags ) ) { |
||
| 433 | // Can't load from ID |
||
| 434 | return false; |
||
| 435 | } |
||
| 436 | } else { |
||
| 437 | $this->loadFromCache(); |
||
| 438 | } |
||
| 439 | |||
| 440 | $this->mLoadedItems = true; |
||
| 441 | $this->queryFlagsUsed = $flags; |
||
| 442 | |||
| 443 | return true; |
||
| 444 | } |
||
| 445 | |||
| 446 | /** |
||
| 447 | * @since 1.27 |
||
| 448 | * @param string $wikiId |
||
| 449 | * @param integer $userId |
||
| 450 | */ |
||
| 451 | public static function purge( $wikiId, $userId ) { |
||
| 452 | $cache = ObjectCache::getMainWANInstance(); |
||
| 453 | $key = $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId ); |
||
| 454 | $cache->delete( $key ); |
||
| 455 | } |
||
| 456 | |||
| 457 | /** |
||
| 458 | * @since 1.27 |
||
| 459 | * @param WANObjectCache $cache |
||
| 460 | * @return string |
||
| 461 | */ |
||
| 462 | protected function getCacheKey( WANObjectCache $cache ) { |
||
| 463 | return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId ); |
||
| 464 | } |
||
| 465 | |||
| 466 | /** |
||
| 467 | * Load user data from shared cache, given mId has already been set. |
||
| 468 | * |
||
| 469 | * @return bool True |
||
| 470 | * @since 1.25 |
||
| 471 | */ |
||
| 472 | protected function loadFromCache() { |
||
| 473 | $cache = ObjectCache::getMainWANInstance(); |
||
| 474 | $data = $cache->getWithSetCallback( |
||
| 475 | $this->getCacheKey( $cache ), |
||
| 476 | $cache::TTL_HOUR, |
||
| 477 | function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) { |
||
| 478 | $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) ); |
||
| 479 | wfDebug( "User: cache miss for user {$this->mId}\n" ); |
||
| 480 | |||
| 481 | $this->loadFromDatabase( self::READ_NORMAL ); |
||
| 482 | $this->loadGroups(); |
||
| 483 | $this->loadOptions(); |
||
| 484 | |||
| 485 | $data = []; |
||
| 486 | foreach ( self::$mCacheVars as $name ) { |
||
| 487 | $data[$name] = $this->$name; |
||
| 488 | } |
||
| 489 | |||
| 490 | $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->mTouched ), $ttl ); |
||
| 491 | |||
| 492 | return $data; |
||
| 493 | |||
| 494 | }, |
||
| 495 | [ 'pcTTL' => $cache::TTL_PROC_LONG, 'version' => self::VERSION ] |
||
| 496 | ); |
||
| 497 | |||
| 498 | // Restore from cache |
||
| 499 | foreach ( self::$mCacheVars as $name ) { |
||
| 500 | $this->$name = $data[$name]; |
||
| 501 | } |
||
| 502 | |||
| 503 | return true; |
||
| 504 | } |
||
| 505 | |||
| 506 | /** @name newFrom*() static factory methods */ |
||
| 507 | // @{ |
||
| 508 | |||
| 509 | /** |
||
| 510 | * Static factory method for creation from username. |
||
| 511 | * |
||
| 512 | * This is slightly less efficient than newFromId(), so use newFromId() if |
||
| 513 | * you have both an ID and a name handy. |
||
| 514 | * |
||
| 515 | * @param string $name Username, validated by Title::newFromText() |
||
| 516 | * @param string|bool $validate Validate username. Takes the same parameters as |
||
| 517 | * User::getCanonicalName(), except that true is accepted as an alias |
||
| 518 | * for 'valid', for BC. |
||
| 519 | * |
||
| 520 | * @return User|bool User object, or false if the username is invalid |
||
| 521 | * (e.g. if it contains illegal characters or is an IP address). If the |
||
| 522 | * username is not present in the database, the result will be a user object |
||
| 523 | * with a name, zero user ID and default settings. |
||
| 524 | */ |
||
| 525 | public static function newFromName( $name, $validate = 'valid' ) { |
||
| 526 | if ( $validate === true ) { |
||
| 527 | $validate = 'valid'; |
||
| 528 | } |
||
| 529 | $name = self::getCanonicalName( $name, $validate ); |
||
| 530 | if ( $name === false ) { |
||
| 531 | return false; |
||
| 532 | } else { |
||
| 533 | // Create unloaded user object |
||
| 534 | $u = new User; |
||
| 535 | $u->mName = $name; |
||
| 536 | $u->mFrom = 'name'; |
||
| 537 | $u->setItemLoaded( 'name' ); |
||
| 538 | return $u; |
||
| 539 | } |
||
| 540 | } |
||
| 541 | |||
| 542 | /** |
||
| 543 | * Static factory method for creation from a given user ID. |
||
| 544 | * |
||
| 545 | * @param int $id Valid user ID |
||
| 546 | * @return User The corresponding User object |
||
| 547 | */ |
||
| 548 | public static function newFromId( $id ) { |
||
| 549 | $u = new User; |
||
| 550 | $u->mId = $id; |
||
| 551 | $u->mFrom = 'id'; |
||
| 552 | $u->setItemLoaded( 'id' ); |
||
| 553 | return $u; |
||
| 554 | } |
||
| 555 | |||
| 556 | /** |
||
| 557 | * Factory method to fetch whichever user has a given email confirmation code. |
||
| 558 | * This code is generated when an account is created or its e-mail address |
||
| 559 | * has changed. |
||
| 560 | * |
||
| 561 | * If the code is invalid or has expired, returns NULL. |
||
| 562 | * |
||
| 563 | * @param string $code Confirmation code |
||
| 564 | * @param int $flags User::READ_* bitfield |
||
| 565 | * @return User|null |
||
| 566 | */ |
||
| 567 | public static function newFromConfirmationCode( $code, $flags = 0 ) { |
||
| 568 | $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST |
||
| 569 | ? wfGetDB( DB_MASTER ) |
||
| 570 | : wfGetDB( DB_REPLICA ); |
||
| 571 | |||
| 572 | $id = $db->selectField( |
||
| 573 | 'user', |
||
| 574 | 'user_id', |
||
| 575 | [ |
||
| 576 | 'user_email_token' => md5( $code ), |
||
| 577 | 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ), |
||
| 578 | ] |
||
| 579 | ); |
||
| 580 | |||
| 581 | return $id ? User::newFromId( $id ) : null; |
||
| 582 | } |
||
| 583 | |||
| 584 | /** |
||
| 585 | * Create a new user object using data from session. If the login |
||
| 586 | * credentials are invalid, the result is an anonymous user. |
||
| 587 | * |
||
| 588 | * @param WebRequest|null $request Object to use; $wgRequest will be used if omitted. |
||
| 589 | * @return User |
||
| 590 | */ |
||
| 591 | public static function newFromSession( WebRequest $request = null ) { |
||
| 592 | $user = new User; |
||
| 593 | $user->mFrom = 'session'; |
||
| 594 | $user->mRequest = $request; |
||
| 595 | return $user; |
||
| 596 | } |
||
| 597 | |||
| 598 | /** |
||
| 599 | * Create a new user object from a user row. |
||
| 600 | * The row should have the following fields from the user table in it: |
||
| 601 | * - either user_name or user_id to load further data if needed (or both) |
||
| 602 | * - user_real_name |
||
| 603 | * - all other fields (email, etc.) |
||
| 604 | * It is useless to provide the remaining fields if either user_id, |
||
| 605 | * user_name and user_real_name are not provided because the whole row |
||
| 606 | * will be loaded once more from the database when accessing them. |
||
| 607 | * |
||
| 608 | * @param stdClass $row A row from the user table |
||
| 609 | * @param array $data Further data to load into the object (see User::loadFromRow for valid keys) |
||
| 610 | * @return User |
||
| 611 | */ |
||
| 612 | public static function newFromRow( $row, $data = null ) { |
||
| 613 | $user = new User; |
||
| 614 | $user->loadFromRow( $row, $data ); |
||
| 615 | return $user; |
||
| 616 | } |
||
| 617 | |||
| 618 | /** |
||
| 619 | * Static factory method for creation of a "system" user from username. |
||
| 620 | * |
||
| 621 | * A "system" user is an account that's used to attribute logged actions |
||
| 622 | * taken by MediaWiki itself, as opposed to a bot or human user. Examples |
||
| 623 | * might include the 'Maintenance script' or 'Conversion script' accounts |
||
| 624 | * used by various scripts in the maintenance/ directory or accounts such |
||
| 625 | * as 'MediaWiki message delivery' used by the MassMessage extension. |
||
| 626 | * |
||
| 627 | * This can optionally create the user if it doesn't exist, and "steal" the |
||
| 628 | * account if it does exist. |
||
| 629 | * |
||
| 630 | * "Stealing" an existing user is intended to make it impossible for normal |
||
| 631 | * authentication processes to use the account, effectively disabling the |
||
| 632 | * account for normal use: |
||
| 633 | * - Email is invalidated, to prevent account recovery by emailing a |
||
| 634 | * temporary password and to disassociate the account from the existing |
||
| 635 | * human. |
||
| 636 | * - The token is set to a magic invalid value, to kill existing sessions |
||
| 637 | * and to prevent $this->setToken() calls from resetting the token to a |
||
| 638 | * valid value. |
||
| 639 | * - SessionManager is instructed to prevent new sessions for the user, to |
||
| 640 | * do things like deauthorizing OAuth consumers. |
||
| 641 | * - AuthManager is instructed to revoke access, to invalidate or remove |
||
| 642 | * passwords and other credentials. |
||
| 643 | * |
||
| 644 | * @param string $name Username |
||
| 645 | * @param array $options Options are: |
||
| 646 | * - validate: As for User::getCanonicalName(), default 'valid' |
||
| 647 | * - create: Whether to create the user if it doesn't already exist, default true |
||
| 648 | * - steal: Whether to "disable" the account for normal use if it already |
||
| 649 | * exists, default false |
||
| 650 | * @return User|null |
||
| 651 | * @since 1.27 |
||
| 652 | */ |
||
| 653 | public static function newSystemUser( $name, $options = [] ) { |
||
| 654 | $options += [ |
||
| 655 | 'validate' => 'valid', |
||
| 656 | 'create' => true, |
||
| 657 | 'steal' => false, |
||
| 658 | ]; |
||
| 659 | |||
| 660 | $name = self::getCanonicalName( $name, $options['validate'] ); |
||
| 661 | if ( $name === false ) { |
||
| 662 | return null; |
||
| 663 | } |
||
| 664 | |||
| 665 | $fields = self::selectFields(); |
||
| 666 | |||
| 667 | $dbw = wfGetDB( DB_MASTER ); |
||
| 668 | $row = $dbw->selectRow( |
||
| 669 | 'user', |
||
| 670 | $fields, |
||
| 671 | [ 'user_name' => $name ], |
||
| 672 | __METHOD__ |
||
| 673 | ); |
||
| 674 | if ( !$row ) { |
||
| 675 | // No user. Create it? |
||
| 676 | return $options['create'] ? self::createNew( $name ) : null; |
||
| 677 | } |
||
| 678 | $user = self::newFromRow( $row ); |
||
| 679 | |||
| 680 | // A user is considered to exist as a non-system user if it can |
||
| 681 | // authenticate, or has an email set, or has a non-invalid token. |
||
| 682 | if ( $user->mEmail || $user->mToken !== self::INVALID_TOKEN || |
||
| 683 | AuthManager::singleton()->userCanAuthenticate( $name ) |
||
| 684 | ) { |
||
| 685 | // User exists. Steal it? |
||
| 686 | if ( !$options['steal'] ) { |
||
| 687 | return null; |
||
| 688 | } |
||
| 689 | |||
| 690 | AuthManager::singleton()->revokeAccessForUser( $name ); |
||
| 691 | |||
| 692 | $user->invalidateEmail(); |
||
| 693 | $user->mToken = self::INVALID_TOKEN; |
||
| 694 | $user->saveSettings(); |
||
| 695 | SessionManager::singleton()->preventSessionsForUser( $user->getName() ); |
||
| 696 | } |
||
| 697 | |||
| 698 | return $user; |
||
| 699 | } |
||
| 700 | |||
| 701 | // @} |
||
| 702 | |||
| 703 | /** |
||
| 704 | * Get the username corresponding to a given user ID |
||
| 705 | * @param int $id User ID |
||
| 706 | * @return string|bool The corresponding username |
||
| 707 | */ |
||
| 708 | public static function whoIs( $id ) { |
||
| 709 | return UserCache::singleton()->getProp( $id, 'name' ); |
||
| 710 | } |
||
| 711 | |||
| 712 | /** |
||
| 713 | * Get the real name of a user given their user ID |
||
| 714 | * |
||
| 715 | * @param int $id User ID |
||
| 716 | * @return string|bool The corresponding user's real name |
||
| 717 | */ |
||
| 718 | public static function whoIsReal( $id ) { |
||
| 719 | return UserCache::singleton()->getProp( $id, 'real_name' ); |
||
| 720 | } |
||
| 721 | |||
| 722 | /** |
||
| 723 | * Get database id given a user name |
||
| 724 | * @param string $name Username |
||
| 725 | * @param integer $flags User::READ_* constant bitfield |
||
| 726 | * @return int|null The corresponding user's ID, or null if user is nonexistent |
||
| 727 | */ |
||
| 728 | public static function idFromName( $name, $flags = self::READ_NORMAL ) { |
||
| 729 | $nt = Title::makeTitleSafe( NS_USER, $name ); |
||
| 730 | if ( is_null( $nt ) ) { |
||
| 731 | // Illegal name |
||
| 732 | return null; |
||
| 733 | } |
||
| 734 | |||
| 735 | if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) { |
||
| 736 | return self::$idCacheByName[$name]; |
||
| 737 | } |
||
| 738 | |||
| 739 | list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags ); |
||
| 740 | $db = wfGetDB( $index ); |
||
| 741 | |||
| 742 | $s = $db->selectRow( |
||
| 743 | 'user', |
||
| 744 | [ 'user_id' ], |
||
| 745 | [ 'user_name' => $nt->getText() ], |
||
| 746 | __METHOD__, |
||
| 747 | $options |
||
| 748 | ); |
||
| 749 | |||
| 750 | if ( $s === false ) { |
||
| 751 | $result = null; |
||
| 752 | } else { |
||
| 753 | $result = $s->user_id; |
||
| 754 | } |
||
| 755 | |||
| 756 | self::$idCacheByName[$name] = $result; |
||
| 757 | |||
| 758 | if ( count( self::$idCacheByName ) > 1000 ) { |
||
| 759 | self::$idCacheByName = []; |
||
| 760 | } |
||
| 761 | |||
| 762 | return $result; |
||
| 763 | } |
||
| 764 | |||
| 765 | /** |
||
| 766 | * Reset the cache used in idFromName(). For use in tests. |
||
| 767 | */ |
||
| 768 | public static function resetIdByNameCache() { |
||
| 769 | self::$idCacheByName = []; |
||
| 770 | } |
||
| 771 | |||
| 772 | /** |
||
| 773 | * Does the string match an anonymous IP address? |
||
| 774 | * |
||
| 775 | * This function exists for username validation, in order to reject |
||
| 776 | * usernames which are similar in form to IP addresses. Strings such |
||
| 777 | * as 300.300.300.300 will return true because it looks like an IP |
||
| 778 | * address, despite not being strictly valid. |
||
| 779 | * |
||
| 780 | * We match "\d{1,3}\.\d{1,3}\.\d{1,3}\.xxx" as an anonymous IP |
||
| 781 | * address because the usemod software would "cloak" anonymous IP |
||
| 782 | * addresses like this, if we allowed accounts like this to be created |
||
| 783 | * new users could get the old edits of these anonymous users. |
||
| 784 | * |
||
| 785 | * @param string $name Name to match |
||
| 786 | * @return bool |
||
| 787 | */ |
||
| 788 | public static function isIP( $name ) { |
||
| 789 | return preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/', $name ) |
||
| 790 | || IP::isIPv6( $name ); |
||
| 791 | } |
||
| 792 | |||
| 793 | /** |
||
| 794 | * Is the input a valid username? |
||
| 795 | * |
||
| 796 | * Checks if the input is a valid username, we don't want an empty string, |
||
| 797 | * an IP address, anything that contains slashes (would mess up subpages), |
||
| 798 | * is longer than the maximum allowed username size or doesn't begin with |
||
| 799 | * a capital letter. |
||
| 800 | * |
||
| 801 | * @param string $name Name to match |
||
| 802 | * @return bool |
||
| 803 | */ |
||
| 804 | public static function isValidUserName( $name ) { |
||
| 805 | global $wgContLang, $wgMaxNameChars; |
||
| 806 | |||
| 807 | if ( $name == '' |
||
| 808 | || User::isIP( $name ) |
||
| 809 | || strpos( $name, '/' ) !== false |
||
| 810 | || strlen( $name ) > $wgMaxNameChars |
||
| 811 | || $name != $wgContLang->ucfirst( $name ) |
||
| 812 | ) { |
||
| 813 | return false; |
||
| 814 | } |
||
| 815 | |||
| 816 | // Ensure that the name can't be misresolved as a different title, |
||
| 817 | // such as with extra namespace keys at the start. |
||
| 818 | $parsed = Title::newFromText( $name ); |
||
| 819 | if ( is_null( $parsed ) |
||
| 820 | || $parsed->getNamespace() |
||
| 821 | || strcmp( $name, $parsed->getPrefixedText() ) ) { |
||
| 822 | return false; |
||
| 823 | } |
||
| 824 | |||
| 825 | // Check an additional blacklist of troublemaker characters. |
||
| 826 | // Should these be merged into the title char list? |
||
| 827 | $unicodeBlacklist = '/[' . |
||
| 828 | '\x{0080}-\x{009f}' . # iso-8859-1 control chars |
||
| 829 | '\x{00a0}' . # non-breaking space |
||
| 830 | '\x{2000}-\x{200f}' . # various whitespace |
||
| 831 | '\x{2028}-\x{202f}' . # breaks and control chars |
||
| 832 | '\x{3000}' . # ideographic space |
||
| 833 | '\x{e000}-\x{f8ff}' . # private use |
||
| 834 | ']/u'; |
||
| 835 | if ( preg_match( $unicodeBlacklist, $name ) ) { |
||
| 836 | return false; |
||
| 837 | } |
||
| 838 | |||
| 839 | return true; |
||
| 840 | } |
||
| 841 | |||
| 842 | /** |
||
| 843 | * Usernames which fail to pass this function will be blocked |
||
| 844 | * from user login and new account registrations, but may be used |
||
| 845 | * internally by batch processes. |
||
| 846 | * |
||
| 847 | * If an account already exists in this form, login will be blocked |
||
| 848 | * by a failure to pass this function. |
||
| 849 | * |
||
| 850 | * @param string $name Name to match |
||
| 851 | * @return bool |
||
| 852 | */ |
||
| 853 | public static function isUsableName( $name ) { |
||
| 854 | global $wgReservedUsernames; |
||
| 855 | // Must be a valid username, obviously ;) |
||
| 856 | if ( !self::isValidUserName( $name ) ) { |
||
| 857 | return false; |
||
| 858 | } |
||
| 859 | |||
| 860 | static $reservedUsernames = false; |
||
| 861 | if ( !$reservedUsernames ) { |
||
| 862 | $reservedUsernames = $wgReservedUsernames; |
||
| 863 | Hooks::run( 'UserGetReservedNames', [ &$reservedUsernames ] ); |
||
| 864 | } |
||
| 865 | |||
| 866 | // Certain names may be reserved for batch processes. |
||
| 867 | foreach ( $reservedUsernames as $reserved ) { |
||
| 868 | if ( substr( $reserved, 0, 4 ) == 'msg:' ) { |
||
| 869 | $reserved = wfMessage( substr( $reserved, 4 ) )->inContentLanguage()->text(); |
||
| 870 | } |
||
| 871 | if ( $reserved == $name ) { |
||
| 872 | return false; |
||
| 873 | } |
||
| 874 | } |
||
| 875 | return true; |
||
| 876 | } |
||
| 877 | |||
| 878 | /** |
||
| 879 | * Return the users who are members of the given group(s). In case of multiple groups, |
||
| 880 | * users who are members of at least one of them are returned. |
||
| 881 | * |
||
| 882 | * @param string|array $groups A single group name or an array of group names |
||
| 883 | * @param int $limit Max number of users to return. The actual limit will never exceed 5000 |
||
| 884 | * records; larger values are ignored. |
||
| 885 | * @param int $after ID the user to start after |
||
| 886 | * @return UserArrayFromResult |
||
| 887 | */ |
||
| 888 | public static function findUsersByGroup( $groups, $limit = 5000, $after = null ) { |
||
| 889 | if ( $groups === [] ) { |
||
| 890 | return UserArrayFromResult::newFromIDs( [] ); |
||
| 891 | } |
||
| 892 | |||
| 893 | $groups = array_unique( (array)$groups ); |
||
| 894 | $limit = min( 5000, $limit ); |
||
| 895 | |||
| 896 | $conds = [ 'ug_group' => $groups ]; |
||
| 897 | if ( $after !== null ) { |
||
| 898 | $conds[] = 'ug_user > ' . (int)$after; |
||
| 899 | } |
||
| 900 | |||
| 901 | $dbr = wfGetDB( DB_REPLICA ); |
||
| 902 | $ids = $dbr->selectFieldValues( |
||
| 903 | 'user_groups', |
||
| 904 | 'ug_user', |
||
| 905 | $conds, |
||
| 906 | __METHOD__, |
||
| 907 | [ |
||
| 908 | 'DISTINCT' => true, |
||
| 909 | 'ORDER BY' => 'ug_user', |
||
| 910 | 'LIMIT' => $limit, |
||
| 911 | ] |
||
| 912 | ) ?: []; |
||
| 913 | return UserArray::newFromIDs( $ids ); |
||
| 914 | } |
||
| 915 | |||
| 916 | /** |
||
| 917 | * Usernames which fail to pass this function will be blocked |
||
| 918 | * from new account registrations, but may be used internally |
||
| 919 | * either by batch processes or by user accounts which have |
||
| 920 | * already been created. |
||
| 921 | * |
||
| 922 | * Additional blacklisting may be added here rather than in |
||
| 923 | * isValidUserName() to avoid disrupting existing accounts. |
||
| 924 | * |
||
| 925 | * @param string $name String to match |
||
| 926 | * @return bool |
||
| 927 | */ |
||
| 928 | public static function isCreatableName( $name ) { |
||
| 929 | global $wgInvalidUsernameCharacters; |
||
| 930 | |||
| 931 | // Ensure that the username isn't longer than 235 bytes, so that |
||
| 932 | // (at least for the builtin skins) user javascript and css files |
||
| 933 | // will work. (bug 23080) |
||
| 934 | if ( strlen( $name ) > 235 ) { |
||
| 935 | wfDebugLog( 'username', __METHOD__ . |
||
| 936 | ": '$name' invalid due to length" ); |
||
| 937 | return false; |
||
| 938 | } |
||
| 939 | |||
| 940 | // Preg yells if you try to give it an empty string |
||
| 941 | if ( $wgInvalidUsernameCharacters !== '' ) { |
||
| 942 | if ( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) { |
||
| 943 | wfDebugLog( 'username', __METHOD__ . |
||
| 944 | ": '$name' invalid due to wgInvalidUsernameCharacters" ); |
||
| 945 | return false; |
||
| 946 | } |
||
| 947 | } |
||
| 948 | |||
| 949 | return self::isUsableName( $name ); |
||
| 950 | } |
||
| 951 | |||
| 952 | /** |
||
| 953 | * Is the input a valid password for this user? |
||
| 954 | * |
||
| 955 | * @param string $password Desired password |
||
| 956 | * @return bool |
||
| 957 | */ |
||
| 958 | public function isValidPassword( $password ) { |
||
| 959 | // simple boolean wrapper for getPasswordValidity |
||
| 960 | return $this->getPasswordValidity( $password ) === true; |
||
| 961 | } |
||
| 962 | |||
| 963 | /** |
||
| 964 | * Given unvalidated password input, return error message on failure. |
||
| 965 | * |
||
| 966 | * @param string $password Desired password |
||
| 967 | * @return bool|string|array True on success, string or array of error message on failure |
||
| 968 | */ |
||
| 969 | public function getPasswordValidity( $password ) { |
||
| 970 | $result = $this->checkPasswordValidity( $password ); |
||
| 971 | if ( $result->isGood() ) { |
||
| 972 | return true; |
||
| 973 | } else { |
||
| 974 | $messages = []; |
||
| 975 | foreach ( $result->getErrorsByType( 'error' ) as $error ) { |
||
| 976 | $messages[] = $error['message']; |
||
| 977 | } |
||
| 978 | foreach ( $result->getErrorsByType( 'warning' ) as $warning ) { |
||
| 979 | $messages[] = $warning['message']; |
||
| 980 | } |
||
| 981 | if ( count( $messages ) === 1 ) { |
||
| 982 | return $messages[0]; |
||
| 983 | } |
||
| 984 | return $messages; |
||
| 985 | } |
||
| 986 | } |
||
| 987 | |||
| 988 | /** |
||
| 989 | * Check if this is a valid password for this user |
||
| 990 | * |
||
| 991 | * Create a Status object based on the password's validity. |
||
| 992 | * The Status should be set to fatal if the user should not |
||
| 993 | * be allowed to log in, and should have any errors that |
||
| 994 | * would block changing the password. |
||
| 995 | * |
||
| 996 | * If the return value of this is not OK, the password |
||
| 997 | * should not be checked. If the return value is not Good, |
||
| 998 | * the password can be checked, but the user should not be |
||
| 999 | * able to set their password to this. |
||
| 1000 | * |
||
| 1001 | * @param string $password Desired password |
||
| 1002 | * @param string $purpose one of 'login', 'create', 'reset' |
||
| 1003 | * @return Status |
||
| 1004 | * @since 1.23 |
||
| 1005 | */ |
||
| 1006 | public function checkPasswordValidity( $password, $purpose = 'login' ) { |
||
| 1007 | global $wgPasswordPolicy; |
||
| 1008 | |||
| 1009 | $upp = new UserPasswordPolicy( |
||
| 1010 | $wgPasswordPolicy['policies'], |
||
| 1011 | $wgPasswordPolicy['checks'] |
||
| 1012 | ); |
||
| 1013 | |||
| 1014 | $status = Status::newGood(); |
||
| 1015 | $result = false; // init $result to false for the internal checks |
||
| 1016 | |||
| 1017 | if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) { |
||
| 1018 | $status->error( $result ); |
||
| 1019 | return $status; |
||
| 1020 | } |
||
| 1021 | |||
| 1022 | if ( $result === false ) { |
||
| 1023 | $status->merge( $upp->checkUserPassword( $this, $password, $purpose ) ); |
||
| 1024 | return $status; |
||
| 1025 | } elseif ( $result === true ) { |
||
| 1026 | return $status; |
||
| 1027 | } else { |
||
| 1028 | $status->error( $result ); |
||
| 1029 | return $status; // the isValidPassword hook set a string $result and returned true |
||
| 1030 | } |
||
| 1031 | } |
||
| 1032 | |||
| 1033 | /** |
||
| 1034 | * Given unvalidated user input, return a canonical username, or false if |
||
| 1035 | * the username is invalid. |
||
| 1036 | * @param string $name User input |
||
| 1037 | * @param string|bool $validate Type of validation to use: |
||
| 1038 | * - false No validation |
||
| 1039 | * - 'valid' Valid for batch processes |
||
| 1040 | * - 'usable' Valid for batch processes and login |
||
| 1041 | * - 'creatable' Valid for batch processes, login and account creation |
||
| 1042 | * |
||
| 1043 | * @throws InvalidArgumentException |
||
| 1044 | * @return bool|string |
||
| 1045 | */ |
||
| 1046 | public static function getCanonicalName( $name, $validate = 'valid' ) { |
||
| 1047 | // Force usernames to capital |
||
| 1048 | global $wgContLang; |
||
| 1049 | $name = $wgContLang->ucfirst( $name ); |
||
| 1050 | |||
| 1051 | # Reject names containing '#'; these will be cleaned up |
||
| 1052 | # with title normalisation, but then it's too late to |
||
| 1053 | # check elsewhere |
||
| 1054 | if ( strpos( $name, '#' ) !== false ) { |
||
| 1055 | return false; |
||
| 1056 | } |
||
| 1057 | |||
| 1058 | // Clean up name according to title rules, |
||
| 1059 | // but only when validation is requested (bug 12654) |
||
| 1060 | $t = ( $validate !== false ) ? |
||
| 1061 | Title::newFromText( $name, NS_USER ) : Title::makeTitle( NS_USER, $name ); |
||
| 1062 | // Check for invalid titles |
||
| 1063 | if ( is_null( $t ) || $t->getNamespace() !== NS_USER || $t->isExternal() ) { |
||
| 1064 | return false; |
||
| 1065 | } |
||
| 1066 | |||
| 1067 | // Reject various classes of invalid names |
||
| 1068 | $name = AuthManager::callLegacyAuthPlugin( |
||
| 1069 | 'getCanonicalName', [ $t->getText() ], $t->getText() |
||
| 1070 | ); |
||
| 1071 | |||
| 1072 | switch ( $validate ) { |
||
| 1073 | case false: |
||
| 1074 | break; |
||
| 1075 | case 'valid': |
||
| 1076 | if ( !User::isValidUserName( $name ) ) { |
||
| 1077 | $name = false; |
||
| 1078 | } |
||
| 1079 | break; |
||
| 1080 | case 'usable': |
||
| 1081 | if ( !User::isUsableName( $name ) ) { |
||
| 1082 | $name = false; |
||
| 1083 | } |
||
| 1084 | break; |
||
| 1085 | case 'creatable': |
||
| 1086 | if ( !User::isCreatableName( $name ) ) { |
||
| 1087 | $name = false; |
||
| 1088 | } |
||
| 1089 | break; |
||
| 1090 | default: |
||
| 1091 | throw new InvalidArgumentException( |
||
| 1092 | 'Invalid parameter value for $validate in ' . __METHOD__ ); |
||
| 1093 | } |
||
| 1094 | return $name; |
||
| 1095 | } |
||
| 1096 | |||
| 1097 | /** |
||
| 1098 | * Count the number of edits of a user |
||
| 1099 | * |
||
| 1100 | * @param int $uid User ID to check |
||
| 1101 | * @return int The user's edit count |
||
| 1102 | * |
||
| 1103 | * @deprecated since 1.21 in favour of User::getEditCount |
||
| 1104 | */ |
||
| 1105 | public static function edits( $uid ) { |
||
| 1106 | wfDeprecated( __METHOD__, '1.21' ); |
||
| 1107 | $user = self::newFromId( $uid ); |
||
| 1108 | return $user->getEditCount(); |
||
| 1109 | } |
||
| 1110 | |||
| 1111 | /** |
||
| 1112 | * Return a random password. |
||
| 1113 | * |
||
| 1114 | * @deprecated since 1.27, use PasswordFactory::generateRandomPasswordString() |
||
| 1115 | * @return string New random password |
||
| 1116 | */ |
||
| 1117 | public static function randomPassword() { |
||
| 1118 | global $wgMinimalPasswordLength; |
||
| 1119 | return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength ); |
||
| 1120 | } |
||
| 1121 | |||
| 1122 | /** |
||
| 1123 | * Set cached properties to default. |
||
| 1124 | * |
||
| 1125 | * @note This no longer clears uncached lazy-initialised properties; |
||
| 1126 | * the constructor does that instead. |
||
| 1127 | * |
||
| 1128 | * @param string|bool $name |
||
| 1129 | */ |
||
| 1130 | public function loadDefaults( $name = false ) { |
||
| 1131 | $this->mId = 0; |
||
| 1132 | $this->mName = $name; |
||
| 1133 | $this->mRealName = ''; |
||
| 1134 | $this->mEmail = ''; |
||
| 1135 | $this->mOptionOverrides = null; |
||
| 1136 | $this->mOptionsLoaded = false; |
||
| 1137 | |||
| 1138 | $loggedOut = $this->mRequest && !defined( 'MW_NO_SESSION' ) |
||
| 1139 | ? $this->mRequest->getSession()->getLoggedOutTimestamp() : 0; |
||
| 1140 | if ( $loggedOut !== 0 ) { |
||
| 1141 | $this->mTouched = wfTimestamp( TS_MW, $loggedOut ); |
||
| 1142 | } else { |
||
| 1143 | $this->mTouched = '1'; # Allow any pages to be cached |
||
| 1144 | } |
||
| 1145 | |||
| 1146 | $this->mToken = null; // Don't run cryptographic functions till we need a token |
||
| 1147 | $this->mEmailAuthenticated = null; |
||
| 1148 | $this->mEmailToken = ''; |
||
| 1149 | $this->mEmailTokenExpires = null; |
||
| 1150 | $this->mRegistration = wfTimestamp( TS_MW ); |
||
| 1151 | $this->mGroups = []; |
||
| 1152 | |||
| 1153 | Hooks::run( 'UserLoadDefaults', [ $this, $name ] ); |
||
| 1154 | } |
||
| 1155 | |||
| 1156 | /** |
||
| 1157 | * Return whether an item has been loaded. |
||
| 1158 | * |
||
| 1159 | * @param string $item Item to check. Current possibilities: |
||
| 1160 | * - id |
||
| 1161 | * - name |
||
| 1162 | * - realname |
||
| 1163 | * @param string $all 'all' to check if the whole object has been loaded |
||
| 1164 | * or any other string to check if only the item is available (e.g. |
||
| 1165 | * for optimisation) |
||
| 1166 | * @return bool |
||
| 1167 | */ |
||
| 1168 | public function isItemLoaded( $item, $all = 'all' ) { |
||
| 1169 | return ( $this->mLoadedItems === true && $all === 'all' ) || |
||
| 1170 | ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true ); |
||
| 1171 | } |
||
| 1172 | |||
| 1173 | /** |
||
| 1174 | * Set that an item has been loaded |
||
| 1175 | * |
||
| 1176 | * @param string $item |
||
| 1177 | */ |
||
| 1178 | protected function setItemLoaded( $item ) { |
||
| 1179 | if ( is_array( $this->mLoadedItems ) ) { |
||
| 1180 | $this->mLoadedItems[$item] = true; |
||
| 1181 | } |
||
| 1182 | } |
||
| 1183 | |||
| 1184 | /** |
||
| 1185 | * Load user data from the session. |
||
| 1186 | * |
||
| 1187 | * @return bool True if the user is logged in, false otherwise. |
||
| 1188 | */ |
||
| 1189 | private function loadFromSession() { |
||
| 1190 | // Deprecated hook |
||
| 1191 | $result = null; |
||
| 1192 | Hooks::run( 'UserLoadFromSession', [ $this, &$result ], '1.27' ); |
||
| 1193 | if ( $result !== null ) { |
||
| 1194 | return $result; |
||
| 1195 | } |
||
| 1196 | |||
| 1197 | // MediaWiki\Session\Session already did the necessary authentication of the user |
||
| 1198 | // returned here, so just use it if applicable. |
||
| 1199 | $session = $this->getRequest()->getSession(); |
||
| 1200 | $user = $session->getUser(); |
||
| 1201 | if ( $user->isLoggedIn() ) { |
||
| 1202 | $this->loadFromUserObject( $user ); |
||
| 1203 | // Other code expects these to be set in the session, so set them. |
||
| 1204 | $session->set( 'wsUserID', $this->getId() ); |
||
| 1205 | $session->set( 'wsUserName', $this->getName() ); |
||
| 1206 | $session->set( 'wsToken', $this->getToken() ); |
||
| 1207 | return true; |
||
| 1208 | } |
||
| 1209 | |||
| 1210 | return false; |
||
| 1211 | } |
||
| 1212 | |||
| 1213 | /** |
||
| 1214 | * Load user and user_group data from the database. |
||
| 1215 | * $this->mId must be set, this is how the user is identified. |
||
| 1216 | * |
||
| 1217 | * @param integer $flags User::READ_* constant bitfield |
||
| 1218 | * @return bool True if the user exists, false if the user is anonymous |
||
| 1219 | */ |
||
| 1220 | public function loadFromDatabase( $flags = self::READ_LATEST ) { |
||
| 1221 | // Paranoia |
||
| 1222 | $this->mId = intval( $this->mId ); |
||
| 1223 | |||
| 1224 | if ( !$this->mId ) { |
||
| 1225 | // Anonymous users are not in the database |
||
| 1226 | $this->loadDefaults(); |
||
| 1227 | return false; |
||
| 1228 | } |
||
| 1229 | |||
| 1230 | list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags ); |
||
| 1231 | $db = wfGetDB( $index ); |
||
| 1232 | |||
| 1233 | $s = $db->selectRow( |
||
| 1234 | 'user', |
||
| 1235 | self::selectFields(), |
||
| 1236 | [ 'user_id' => $this->mId ], |
||
| 1237 | __METHOD__, |
||
| 1238 | $options |
||
| 1239 | ); |
||
| 1240 | |||
| 1241 | $this->queryFlagsUsed = $flags; |
||
| 1242 | Hooks::run( 'UserLoadFromDatabase', [ $this, &$s ] ); |
||
| 1243 | |||
| 1244 | if ( $s !== false ) { |
||
| 1245 | // Initialise user table data |
||
| 1246 | $this->loadFromRow( $s ); |
||
| 1247 | $this->mGroups = null; // deferred |
||
| 1248 | $this->getEditCount(); // revalidation for nulls |
||
| 1249 | return true; |
||
| 1250 | } else { |
||
| 1251 | // Invalid user_id |
||
| 1252 | $this->mId = 0; |
||
| 1253 | $this->loadDefaults(); |
||
| 1254 | return false; |
||
| 1255 | } |
||
| 1256 | } |
||
| 1257 | |||
| 1258 | /** |
||
| 1259 | * Initialize this object from a row from the user table. |
||
| 1260 | * |
||
| 1261 | * @param stdClass $row Row from the user table to load. |
||
| 1262 | * @param array $data Further user data to load into the object |
||
| 1263 | * |
||
| 1264 | * user_groups Array with groups out of the user_groups table |
||
| 1265 | * user_properties Array with properties out of the user_properties table |
||
| 1266 | */ |
||
| 1267 | protected function loadFromRow( $row, $data = null ) { |
||
| 1268 | $all = true; |
||
| 1269 | |||
| 1270 | $this->mGroups = null; // deferred |
||
| 1271 | |||
| 1272 | View Code Duplication | if ( isset( $row->user_name ) ) { |
|
| 1273 | $this->mName = $row->user_name; |
||
| 1274 | $this->mFrom = 'name'; |
||
| 1275 | $this->setItemLoaded( 'name' ); |
||
| 1276 | } else { |
||
| 1277 | $all = false; |
||
| 1278 | } |
||
| 1279 | |||
| 1280 | if ( isset( $row->user_real_name ) ) { |
||
| 1281 | $this->mRealName = $row->user_real_name; |
||
| 1282 | $this->setItemLoaded( 'realname' ); |
||
| 1283 | } else { |
||
| 1284 | $all = false; |
||
| 1285 | } |
||
| 1286 | |||
| 1287 | View Code Duplication | if ( isset( $row->user_id ) ) { |
|
| 1288 | $this->mId = intval( $row->user_id ); |
||
| 1289 | $this->mFrom = 'id'; |
||
| 1290 | $this->setItemLoaded( 'id' ); |
||
| 1291 | } else { |
||
| 1292 | $all = false; |
||
| 1293 | } |
||
| 1294 | |||
| 1295 | if ( isset( $row->user_id ) && isset( $row->user_name ) ) { |
||
| 1296 | self::$idCacheByName[$row->user_name] = $row->user_id; |
||
| 1297 | } |
||
| 1298 | |||
| 1299 | if ( isset( $row->user_editcount ) ) { |
||
| 1300 | $this->mEditCount = $row->user_editcount; |
||
| 1301 | } else { |
||
| 1302 | $all = false; |
||
| 1303 | } |
||
| 1304 | |||
| 1305 | if ( isset( $row->user_touched ) ) { |
||
| 1306 | $this->mTouched = wfTimestamp( TS_MW, $row->user_touched ); |
||
| 1307 | } else { |
||
| 1308 | $all = false; |
||
| 1309 | } |
||
| 1310 | |||
| 1311 | if ( isset( $row->user_token ) ) { |
||
| 1312 | // The definition for the column is binary(32), so trim the NULs |
||
| 1313 | // that appends. The previous definition was char(32), so trim |
||
| 1314 | // spaces too. |
||
| 1315 | $this->mToken = rtrim( $row->user_token, " \0" ); |
||
| 1316 | if ( $this->mToken === '' ) { |
||
| 1317 | $this->mToken = null; |
||
| 1318 | } |
||
| 1319 | } else { |
||
| 1320 | $all = false; |
||
| 1321 | } |
||
| 1322 | |||
| 1323 | if ( isset( $row->user_email ) ) { |
||
| 1324 | $this->mEmail = $row->user_email; |
||
| 1325 | $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated ); |
||
| 1326 | $this->mEmailToken = $row->user_email_token; |
||
| 1327 | $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires ); |
||
| 1328 | $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration ); |
||
| 1329 | } else { |
||
| 1330 | $all = false; |
||
| 1331 | } |
||
| 1332 | |||
| 1333 | if ( $all ) { |
||
| 1334 | $this->mLoadedItems = true; |
||
| 1335 | } |
||
| 1336 | |||
| 1337 | if ( is_array( $data ) ) { |
||
| 1338 | if ( isset( $data['user_groups'] ) && is_array( $data['user_groups'] ) ) { |
||
| 1339 | $this->mGroups = $data['user_groups']; |
||
| 1340 | } |
||
| 1341 | if ( isset( $data['user_properties'] ) && is_array( $data['user_properties'] ) ) { |
||
| 1342 | $this->loadOptions( $data['user_properties'] ); |
||
| 1343 | } |
||
| 1344 | } |
||
| 1345 | } |
||
| 1346 | |||
| 1347 | /** |
||
| 1348 | * Load the data for this user object from another user object. |
||
| 1349 | * |
||
| 1350 | * @param User $user |
||
| 1351 | */ |
||
| 1352 | protected function loadFromUserObject( $user ) { |
||
| 1353 | $user->load(); |
||
| 1354 | foreach ( self::$mCacheVars as $var ) { |
||
| 1355 | $this->$var = $user->$var; |
||
| 1356 | } |
||
| 1357 | } |
||
| 1358 | |||
| 1359 | /** |
||
| 1360 | * Load the groups from the database if they aren't already loaded. |
||
| 1361 | */ |
||
| 1362 | View Code Duplication | private function loadGroups() { |
|
| 1377 | |||
| 1378 | /** |
||
| 1379 | * Add the user to the group if he/she meets given criteria. |
||
| 1380 | * |
||
| 1381 | * Contrary to autopromotion by \ref $wgAutopromote, the group will be |
||
| 1382 | * possible to remove manually via Special:UserRights. In such case it |
||
| 1383 | * will not be re-added automatically. The user will also not lose the |
||
| 1384 | * group if they no longer meet the criteria. |
||
| 1385 | * |
||
| 1386 | * @param string $event Key in $wgAutopromoteOnce (each one has groups/criteria) |
||
| 1387 | * |
||
| 1388 | * @return array Array of groups the user has been promoted to. |
||
| 1389 | * |
||
| 1390 | * @see $wgAutopromoteOnce |
||
| 1391 | */ |
||
| 1392 | public function addAutopromoteOnceGroups( $event ) { |
||
| 1432 | |||
| 1433 | /** |
||
| 1434 | * Builds update conditions. Additional conditions may be added to $conditions to |
||
| 1435 | * protected against race conditions using a compare-and-set (CAS) mechanism |
||
| 1436 | * based on comparing $this->mTouched with the user_touched field. |
||
| 1437 | * |
||
| 1438 | * @param Database $db |
||
| 1439 | * @param array $conditions WHERE conditions for use with Database::update |
||
| 1440 | * @return array WHERE conditions for use with Database::update |
||
| 1441 | */ |
||
| 1442 | protected function makeUpdateConditions( Database $db, array $conditions ) { |
||
| 1443 | if ( $this->mTouched ) { |
||
| 1444 | // CAS check: only update if the row wasn't changed sicne it was loaded. |
||
| 1445 | $conditions['user_touched'] = $db->timestamp( $this->mTouched ); |
||
| 1446 | } |
||
| 1447 | |||
| 1448 | return $conditions; |
||
| 1449 | } |
||
| 1450 | |||
| 1451 | /** |
||
| 1452 | * Bump user_touched if it didn't change since this object was loaded |
||
| 1453 | * |
||
| 1454 | * On success, the mTouched field is updated. |
||
| 1455 | * The user serialization cache is always cleared. |
||
| 1456 | * |
||
| 1457 | * @return bool Whether user_touched was actually updated |
||
| 1458 | * @since 1.26 |
||
| 1459 | */ |
||
| 1460 | protected function checkAndSetTouched() { |
||
| 1461 | $this->load(); |
||
| 1462 | |||
| 1463 | if ( !$this->mId ) { |
||
| 1464 | return false; // anon |
||
| 1465 | } |
||
| 1466 | |||
| 1467 | // Get a new user_touched that is higher than the old one |
||
| 1468 | $newTouched = $this->newTouchedTimestamp(); |
||
| 1469 | |||
| 1470 | $dbw = wfGetDB( DB_MASTER ); |
||
| 1471 | $dbw->update( 'user', |
||
| 1472 | [ 'user_touched' => $dbw->timestamp( $newTouched ) ], |
||
| 1473 | $this->makeUpdateConditions( $dbw, [ |
||
| 1474 | 'user_id' => $this->mId, |
||
| 1475 | ] ), |
||
| 1476 | __METHOD__ |
||
| 1477 | ); |
||
| 1478 | $success = ( $dbw->affectedRows() > 0 ); |
||
| 1479 | |||
| 1480 | if ( $success ) { |
||
| 1481 | $this->mTouched = $newTouched; |
||
| 1482 | $this->clearSharedCache(); |
||
| 1483 | } else { |
||
| 1484 | // Clears on failure too since that is desired if the cache is stale |
||
| 1485 | $this->clearSharedCache( 'refresh' ); |
||
| 1486 | } |
||
| 1487 | |||
| 1488 | return $success; |
||
| 1489 | } |
||
| 1490 | |||
| 1491 | /** |
||
| 1492 | * Clear various cached data stored in this object. The cache of the user table |
||
| 1493 | * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given. |
||
| 1494 | * |
||
| 1495 | * @param bool|string $reloadFrom Reload user and user_groups table data from a |
||
| 1496 | * given source. May be "name", "id", "defaults", "session", or false for no reload. |
||
| 1497 | */ |
||
| 1498 | public function clearInstanceCache( $reloadFrom = false ) { |
||
| 1516 | |||
| 1517 | /** |
||
| 1518 | * Combine the language default options with any site-specific options |
||
| 1519 | * and add the default language variants. |
||
| 1520 | * |
||
| 1521 | * @return array Array of String options |
||
| 1522 | */ |
||
| 1523 | public static function getDefaultOptions() { |
||
| 1524 | global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin; |
||
| 1525 | |||
| 1526 | static $defOpt = null; |
||
| 1527 | static $defOptLang = null; |
||
| 1528 | |||
| 1529 | if ( $defOpt !== null && $defOptLang === $wgContLang->getCode() ) { |
||
| 1530 | // $wgContLang does not change (and should not change) mid-request, |
||
| 1531 | // but the unit tests change it anyway, and expect this method to |
||
| 1532 | // return values relevant to the current $wgContLang. |
||
| 1533 | return $defOpt; |
||
| 1534 | } |
||
| 1535 | |||
| 1536 | $defOpt = $wgDefaultUserOptions; |
||
| 1537 | // Default language setting |
||
| 1538 | $defOptLang = $wgContLang->getCode(); |
||
| 1539 | $defOpt['language'] = $defOptLang; |
||
| 1540 | foreach ( LanguageConverter::$languagesWithVariants as $langCode ) { |
||
| 1541 | $defOpt[$langCode == $wgContLang->getCode() ? 'variant' : "variant-$langCode"] = $langCode; |
||
| 1542 | } |
||
| 1543 | |||
| 1544 | // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here, |
||
| 1545 | // since extensions may change the set of searchable namespaces depending |
||
| 1546 | // on user groups/permissions. |
||
| 1547 | foreach ( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) { |
||
| 1548 | $defOpt['searchNs' . $nsnum] = (boolean)$val; |
||
| 1549 | } |
||
| 1550 | $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin ); |
||
| 1551 | |||
| 1552 | Hooks::run( 'UserGetDefaultOptions', [ &$defOpt ] ); |
||
| 1553 | |||
| 1554 | return $defOpt; |
||
| 1555 | } |
||
| 1556 | |||
| 1557 | /** |
||
| 1558 | * Get a given default option value. |
||
| 1559 | * |
||
| 1560 | * @param string $opt Name of option to retrieve |
||
| 1561 | * @return string Default option value |
||
| 1562 | */ |
||
| 1563 | public static function getDefaultOption( $opt ) { |
||
| 1564 | $defOpts = self::getDefaultOptions(); |
||
| 1565 | if ( isset( $defOpts[$opt] ) ) { |
||
| 1566 | return $defOpts[$opt]; |
||
| 1567 | } else { |
||
| 1568 | return null; |
||
| 1569 | } |
||
| 1570 | } |
||
| 1571 | |||
| 1572 | /** |
||
| 1573 | * Get blocking information |
||
| 1574 | * @param bool $bFromSlave Whether to check the replica DB first. |
||
| 1575 | * To improve performance, non-critical checks are done against replica DBs. |
||
| 1576 | * Check when actually saving should be done against master. |
||
| 1577 | */ |
||
| 1578 | private function getBlockedStatus( $bFromSlave = true ) { |
||
| 1579 | global $wgProxyWhitelist, $wgUser, $wgApplyIpBlocksToXff; |
||
| 1580 | |||
| 1581 | if ( -1 != $this->mBlockedby ) { |
||
| 1582 | return; |
||
| 1583 | } |
||
| 1584 | |||
| 1585 | wfDebug( __METHOD__ . ": checking...\n" ); |
||
| 1586 | |||
| 1587 | // Initialize data... |
||
| 1588 | // Otherwise something ends up stomping on $this->mBlockedby when |
||
| 1589 | // things get lazy-loaded later, causing false positive block hits |
||
| 1590 | // due to -1 !== 0. Probably session-related... Nothing should be |
||
| 1591 | // overwriting mBlockedby, surely? |
||
| 1592 | $this->load(); |
||
| 1593 | |||
| 1594 | # We only need to worry about passing the IP address to the Block generator if the |
||
| 1595 | # user is not immune to autoblocks/hardblocks, and they are the current user so we |
||
| 1596 | # know which IP address they're actually coming from |
||
| 1597 | $ip = null; |
||
| 1598 | if ( !$this->isAllowed( 'ipblock-exempt' ) ) { |
||
| 1599 | // $wgUser->getName() only works after the end of Setup.php. Until |
||
| 1600 | // then, assume it's a logged-out user. |
||
| 1601 | $globalUserName = $wgUser->isSafeToLoad() |
||
| 1602 | ? $wgUser->getName() |
||
| 1603 | : IP::sanitizeIP( $wgUser->getRequest()->getIP() ); |
||
| 1604 | if ( $this->getName() === $globalUserName ) { |
||
| 1605 | $ip = $this->getRequest()->getIP(); |
||
| 1606 | } |
||
| 1607 | } |
||
| 1608 | |||
| 1609 | // User/IP blocking |
||
| 1610 | $block = Block::newFromTarget( $this, $ip, !$bFromSlave ); |
||
| 1611 | |||
| 1612 | // Proxy blocking |
||
| 1613 | if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) { |
||
| 1614 | // Local list |
||
| 1615 | if ( self::isLocallyBlockedProxy( $ip ) ) { |
||
| 1616 | $block = new Block; |
||
| 1617 | $block->setBlocker( wfMessage( 'proxyblocker' )->text() ); |
||
| 1618 | $block->mReason = wfMessage( 'proxyblockreason' )->text(); |
||
| 1619 | $block->setTarget( $ip ); |
||
| 1620 | } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) { |
||
| 1621 | $block = new Block; |
||
| 1622 | $block->setBlocker( wfMessage( 'sorbs' )->text() ); |
||
| 1623 | $block->mReason = wfMessage( 'sorbsreason' )->text(); |
||
| 1624 | $block->setTarget( $ip ); |
||
| 1625 | } |
||
| 1626 | } |
||
| 1627 | |||
| 1628 | // (bug 23343) Apply IP blocks to the contents of XFF headers, if enabled |
||
| 1629 | if ( !$block instanceof Block |
||
| 1630 | && $wgApplyIpBlocksToXff |
||
| 1631 | && $ip !== null |
||
| 1632 | && !in_array( $ip, $wgProxyWhitelist ) |
||
| 1633 | ) { |
||
| 1634 | $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' ); |
||
| 1635 | $xff = array_map( 'trim', explode( ',', $xff ) ); |
||
| 1636 | $xff = array_diff( $xff, [ $ip ] ); |
||
| 1637 | $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$bFromSlave ); |
||
| 1638 | $block = Block::chooseBlock( $xffblocks, $xff ); |
||
| 1639 | if ( $block instanceof Block ) { |
||
| 1640 | # Mangle the reason to alert the user that the block |
||
| 1641 | # originated from matching the X-Forwarded-For header. |
||
| 1642 | $block->mReason = wfMessage( 'xffblockreason', $block->mReason )->text(); |
||
| 1643 | } |
||
| 1644 | } |
||
| 1645 | |||
| 1646 | if ( $block instanceof Block ) { |
||
| 1647 | wfDebug( __METHOD__ . ": Found block.\n" ); |
||
| 1648 | $this->mBlock = $block; |
||
| 1649 | $this->mBlockedby = $block->getByName(); |
||
| 1650 | $this->mBlockreason = $block->mReason; |
||
| 1651 | $this->mHideName = $block->mHideName; |
||
| 1652 | $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' ); |
||
| 1653 | } else { |
||
| 1654 | $this->mBlockedby = ''; |
||
| 1655 | $this->mHideName = 0; |
||
| 1656 | $this->mAllowUsertalk = false; |
||
| 1657 | } |
||
| 1658 | |||
| 1659 | // Extensions |
||
| 1660 | Hooks::run( 'GetBlockedStatus', [ &$this ] ); |
||
| 1661 | } |
||
| 1662 | |||
| 1663 | /** |
||
| 1664 | * Whether the given IP is in a DNS blacklist. |
||
| 1665 | * |
||
| 1666 | * @param string $ip IP to check |
||
| 1667 | * @param bool $checkWhitelist Whether to check the whitelist first |
||
| 1668 | * @return bool True if blacklisted. |
||
| 1669 | */ |
||
| 1670 | public function isDnsBlacklisted( $ip, $checkWhitelist = false ) { |
||
| 1683 | |||
| 1684 | /** |
||
| 1685 | * Whether the given IP is in a given DNS blacklist. |
||
| 1686 | * |
||
| 1687 | * @param string $ip IP to check |
||
| 1688 | * @param string|array $bases Array of Strings: URL of the DNS blacklist |
||
| 1689 | * @return bool True if blacklisted. |
||
| 1690 | */ |
||
| 1691 | public function inDnsBlacklist( $ip, $bases ) { |
||
| 1692 | $found = false; |
||
| 1693 | // @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170) |
||
| 1694 | if ( IP::isIPv4( $ip ) ) { |
||
| 1695 | // Reverse IP, bug 21255 |
||
| 1696 | $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) ); |
||
| 1697 | |||
| 1698 | foreach ( (array)$bases as $base ) { |
||
| 1699 | // Make hostname |
||
| 1700 | // If we have an access key, use that too (ProjectHoneypot, etc.) |
||
| 1701 | $basename = $base; |
||
| 1702 | if ( is_array( $base ) ) { |
||
| 1703 | if ( count( $base ) >= 2 ) { |
||
| 1704 | // Access key is 1, base URL is 0 |
||
| 1705 | $host = "{$base[1]}.$ipReversed.{$base[0]}"; |
||
| 1706 | } else { |
||
| 1707 | $host = "$ipReversed.{$base[0]}"; |
||
| 1708 | } |
||
| 1709 | $basename = $base[0]; |
||
| 1710 | } else { |
||
| 1711 | $host = "$ipReversed.$base"; |
||
| 1712 | } |
||
| 1713 | |||
| 1714 | // Send query |
||
| 1715 | $ipList = gethostbynamel( $host ); |
||
| 1716 | |||
| 1717 | if ( $ipList ) { |
||
| 1718 | wfDebugLog( 'dnsblacklist', "Hostname $host is {$ipList[0]}, it's a proxy says $basename!" ); |
||
| 1719 | $found = true; |
||
| 1720 | break; |
||
| 1721 | } else { |
||
| 1722 | wfDebugLog( 'dnsblacklist', "Requested $host, not found in $basename." ); |
||
| 1729 | |||
| 1730 | /** |
||
| 1731 | * Check if an IP address is in the local proxy list |
||
| 1732 | * |
||
| 1733 | * @param string $ip |
||
| 1734 | * |
||
| 1735 | * @return bool |
||
| 1736 | */ |
||
| 1737 | public static function isLocallyBlockedProxy( $ip ) { |
||
| 1761 | |||
| 1762 | /** |
||
| 1763 | * Is this user subject to rate limiting? |
||
| 1764 | * |
||
| 1765 | * @return bool True if rate limited |
||
| 1766 | */ |
||
| 1767 | public function isPingLimitable() { |
||
| 1777 | |||
| 1778 | /** |
||
| 1779 | * Primitive rate limits: enforce maximum actions per time period |
||
| 1780 | * to put a brake on flooding. |
||
| 1781 | * |
||
| 1782 | * The method generates both a generic profiling point and a per action one |
||
| 1783 | * (suffix being "-$action". |
||
| 1784 | * |
||
| 1785 | * @note When using a shared cache like memcached, IP-address |
||
| 1786 | * last-hit counters will be shared across wikis. |
||
| 1787 | * |
||
| 1788 | * @param string $action Action to enforce; 'edit' if unspecified |
||
| 1789 | * @param int $incrBy Positive amount to increment counter by [defaults to 1] |
||
| 1790 | * @return bool True if a rate limiter was tripped |
||
| 1791 | */ |
||
| 1792 | public function pingLimiter( $action = 'edit', $incrBy = 1 ) { |
||
| 1924 | |||
| 1925 | /** |
||
| 1926 | * Check if user is blocked |
||
| 1927 | * |
||
| 1928 | * @param bool $bFromSlave Whether to check the replica DB instead of |
||
| 1929 | * the master. Hacked from false due to horrible probs on site. |
||
| 1930 | * @return bool True if blocked, false otherwise |
||
| 1931 | */ |
||
| 1932 | public function isBlocked( $bFromSlave = true ) { |
||
| 1933 | return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' ); |
||
| 1934 | } |
||
| 1935 | |||
| 1936 | /** |
||
| 1937 | * Get the block affecting the user, or null if the user is not blocked |
||
| 1938 | * |
||
| 1939 | * @param bool $bFromSlave Whether to check the replica DB instead of the master |
||
| 1940 | * @return Block|null |
||
| 1941 | */ |
||
| 1942 | public function getBlock( $bFromSlave = true ) { |
||
| 1946 | |||
| 1947 | /** |
||
| 1948 | * Check if user is blocked from editing a particular article |
||
| 1949 | * |
||
| 1950 | * @param Title $title Title to check |
||
| 1951 | * @param bool $bFromSlave Whether to check the replica DB instead of the master |
||
| 1952 | * @return bool |
||
| 1953 | */ |
||
| 1954 | public function isBlockedFrom( $title, $bFromSlave = false ) { |
||
| 1970 | |||
| 1971 | /** |
||
| 1972 | * If user is blocked, return the name of the user who placed the block |
||
| 1973 | * @return string Name of blocker |
||
| 1974 | */ |
||
| 1975 | public function blockedBy() { |
||
| 1979 | |||
| 1980 | /** |
||
| 1981 | * If user is blocked, return the specified reason for the block |
||
| 1982 | * @return string Blocking reason |
||
| 1983 | */ |
||
| 1984 | public function blockedFor() { |
||
| 1988 | |||
| 1989 | /** |
||
| 1990 | * If user is blocked, return the ID for the block |
||
| 1991 | * @return int Block ID |
||
| 1992 | */ |
||
| 1993 | public function getBlockId() { |
||
| 1997 | |||
| 1998 | /** |
||
| 1999 | * Check if user is blocked on all wikis. |
||
| 2000 | * Do not use for actual edit permission checks! |
||
| 2001 | * This is intended for quick UI checks. |
||
| 2002 | * |
||
| 2003 | * @param string $ip IP address, uses current client if none given |
||
| 2004 | * @return bool True if blocked, false otherwise |
||
| 2005 | */ |
||
| 2006 | public function isBlockedGlobally( $ip = '' ) { |
||
| 2009 | |||
| 2010 | /** |
||
| 2011 | * Check if user is blocked on all wikis. |
||
| 2012 | * Do not use for actual edit permission checks! |
||
| 2013 | * This is intended for quick UI checks. |
||
| 2014 | * |
||
| 2015 | * @param string $ip IP address, uses current client if none given |
||
| 2016 | * @return Block|null Block object if blocked, null otherwise |
||
| 2017 | * @throws FatalError |
||
| 2018 | * @throws MWException |
||
| 2019 | */ |
||
| 2020 | public function getGlobalBlock( $ip = '' ) { |
||
| 2043 | |||
| 2044 | /** |
||
| 2045 | * Check if user account is locked |
||
| 2046 | * |
||
| 2047 | * @return bool True if locked, false otherwise |
||
| 2048 | */ |
||
| 2049 | View Code Duplication | public function isLocked() { |
|
| 2058 | |||
| 2059 | /** |
||
| 2060 | * Check if user account is hidden |
||
| 2061 | * |
||
| 2062 | * @return bool True if hidden, false otherwise |
||
| 2063 | */ |
||
| 2064 | View Code Duplication | public function isHidden() { |
|
| 2076 | |||
| 2077 | /** |
||
| 2078 | * Get the user's ID. |
||
| 2079 | * @return int The user's ID; 0 if the user is anonymous or nonexistent |
||
| 2080 | */ |
||
| 2081 | public function getId() { |
||
| 2092 | |||
| 2093 | /** |
||
| 2094 | * Set the user and reload all fields according to a given ID |
||
| 2095 | * @param int $v User ID to reload |
||
| 2096 | */ |
||
| 2097 | public function setId( $v ) { |
||
| 2101 | |||
| 2102 | /** |
||
| 2103 | * Get the user name, or the IP of an anonymous user |
||
| 2104 | * @return string User's name or IP address |
||
| 2105 | */ |
||
| 2106 | public function getName() { |
||
| 2119 | |||
| 2120 | /** |
||
| 2121 | * Set the user name. |
||
| 2122 | * |
||
| 2123 | * This does not reload fields from the database according to the given |
||
| 2124 | * name. Rather, it is used to create a temporary "nonexistent user" for |
||
| 2125 | * later addition to the database. It can also be used to set the IP |
||
| 2126 | * address for an anonymous user to something other than the current |
||
| 2127 | * remote IP. |
||
| 2128 | * |
||
| 2129 | * @note User::newFromName() has roughly the same function, when the named user |
||
| 2130 | * does not exist. |
||
| 2131 | * @param string $str New user name to set |
||
| 2132 | */ |
||
| 2133 | public function setName( $str ) { |
||
| 2137 | |||
| 2138 | /** |
||
| 2139 | * Get the user's name escaped by underscores. |
||
| 2140 | * @return string Username escaped by underscores. |
||
| 2141 | */ |
||
| 2142 | public function getTitleKey() { |
||
| 2145 | |||
| 2146 | /** |
||
| 2147 | * Check if the user has new messages. |
||
| 2148 | * @return bool True if the user has new messages |
||
| 2149 | */ |
||
| 2150 | public function getNewtalk() { |
||
| 2174 | |||
| 2175 | /** |
||
| 2176 | * Return the data needed to construct links for new talk page message |
||
| 2177 | * alerts. If there are new messages, this will return an associative array |
||
| 2178 | * with the following data: |
||
| 2179 | * wiki: The database name of the wiki |
||
| 2180 | * link: Root-relative link to the user's talk page |
||
| 2181 | * rev: The last talk page revision that the user has seen or null. This |
||
| 2182 | * is useful for building diff links. |
||
| 2183 | * If there are no new messages, it returns an empty array. |
||
| 2184 | * @note This function was designed to accomodate multiple talk pages, but |
||
| 2185 | * currently only returns a single link and revision. |
||
| 2186 | * @return array |
||
| 2187 | */ |
||
| 2188 | public function getNewMessageLinks() { |
||
| 2205 | |||
| 2206 | /** |
||
| 2207 | * Get the revision ID for the last talk page revision viewed by the talk |
||
| 2208 | * page owner. |
||
| 2209 | * @return int|null Revision ID or null |
||
| 2210 | */ |
||
| 2211 | public function getNewMessageRevisionId() { |
||
| 2229 | |||
| 2230 | /** |
||
| 2231 | * Internal uncached check for new messages |
||
| 2232 | * |
||
| 2233 | * @see getNewtalk() |
||
| 2234 | * @param string $field 'user_ip' for anonymous users, 'user_id' otherwise |
||
| 2235 | * @param string|int $id User's IP address for anonymous users, User ID otherwise |
||
| 2236 | * @return bool True if the user has new messages |
||
| 2237 | */ |
||
| 2238 | protected function checkNewtalk( $field, $id ) { |
||
| 2245 | |||
| 2246 | /** |
||
| 2247 | * Add or update the new messages flag |
||
| 2248 | * @param string $field 'user_ip' for anonymous users, 'user_id' otherwise |
||
| 2249 | * @param string|int $id User's IP address for anonymous users, User ID otherwise |
||
| 2250 | * @param Revision|null $curRev New, as yet unseen revision of the user talk page. Ignored if null. |
||
| 2251 | * @return bool True if successful, false otherwise |
||
| 2252 | */ |
||
| 2253 | protected function updateNewtalk( $field, $id, $curRev = null ) { |
||
| 2271 | |||
| 2272 | /** |
||
| 2273 | * Clear the new messages flag for the given user |
||
| 2274 | * @param string $field 'user_ip' for anonymous users, 'user_id' otherwise |
||
| 2275 | * @param string|int $id User's IP address for anonymous users, User ID otherwise |
||
| 2276 | * @return bool True if successful, false otherwise |
||
| 2277 | */ |
||
| 2278 | protected function deleteNewtalk( $field, $id ) { |
||
| 2291 | |||
| 2292 | /** |
||
| 2293 | * Update the 'You have new messages!' status. |
||
| 2294 | * @param bool $val Whether the user has new messages |
||
| 2295 | * @param Revision $curRev New, as yet unseen revision of the user talk |
||
| 2296 | * page. Ignored if null or !$val. |
||
| 2297 | */ |
||
| 2298 | public function setNewtalk( $val, $curRev = null ) { |
||
| 2324 | |||
| 2325 | /** |
||
| 2326 | * Generate a current or new-future timestamp to be stored in the |
||
| 2327 | * user_touched field when we update things. |
||
| 2328 | * @return string Timestamp in TS_MW format |
||
| 2329 | */ |
||
| 2330 | private function newTouchedTimestamp() { |
||
| 2340 | |||
| 2341 | /** |
||
| 2342 | * Clear user data from memcached |
||
| 2343 | * |
||
| 2344 | * Use after applying updates to the database; caller's |
||
| 2345 | * responsibility to update user_touched if appropriate. |
||
| 2346 | * |
||
| 2347 | * Called implicitly from invalidateCache() and saveSettings(). |
||
| 2348 | * |
||
| 2349 | * @param string $mode Use 'refresh' to clear now; otherwise before DB commit |
||
| 2350 | */ |
||
| 2351 | public function clearSharedCache( $mode = 'changed' ) { |
||
| 2369 | |||
| 2370 | /** |
||
| 2371 | * Immediately touch the user data cache for this account |
||
| 2372 | * |
||
| 2373 | * Calls touch() and removes account data from memcached |
||
| 2374 | */ |
||
| 2375 | public function invalidateCache() { |
||
| 2379 | |||
| 2380 | /** |
||
| 2381 | * Update the "touched" timestamp for the user |
||
| 2382 | * |
||
| 2383 | * This is useful on various login/logout events when making sure that |
||
| 2384 | * a browser or proxy that has multiple tenants does not suffer cache |
||
| 2385 | * pollution where the new user sees the old users content. The value |
||
| 2386 | * of getTouched() is checked when determining 304 vs 200 responses. |
||
| 2387 | * Unlike invalidateCache(), this preserves the User object cache and |
||
| 2388 | * avoids database writes. |
||
| 2389 | * |
||
| 2390 | * @since 1.25 |
||
| 2391 | */ |
||
| 2392 | public function touch() { |
||
| 2400 | |||
| 2401 | /** |
||
| 2402 | * Validate the cache for this account. |
||
| 2403 | * @param string $timestamp A timestamp in TS_MW format |
||
| 2404 | * @return bool |
||
| 2405 | */ |
||
| 2406 | public function validateCache( $timestamp ) { |
||
| 2409 | |||
| 2410 | /** |
||
| 2411 | * Get the user touched timestamp |
||
| 2412 | * |
||
| 2413 | * Use this value only to validate caches via inequalities |
||
| 2414 | * such as in the case of HTTP If-Modified-Since response logic |
||
| 2415 | * |
||
| 2416 | * @return string TS_MW Timestamp |
||
| 2417 | */ |
||
| 2418 | public function getTouched() { |
||
| 2434 | |||
| 2435 | /** |
||
| 2436 | * Get the user_touched timestamp field (time of last DB updates) |
||
| 2437 | * @return string TS_MW Timestamp |
||
| 2438 | * @since 1.26 |
||
| 2439 | */ |
||
| 2440 | public function getDBTouched() { |
||
| 2445 | |||
| 2446 | /** |
||
| 2447 | * @deprecated Removed in 1.27. |
||
| 2448 | * @return Password |
||
| 2449 | * @since 1.24 |
||
| 2450 | */ |
||
| 2451 | public function getPassword() { |
||
| 2454 | |||
| 2455 | /** |
||
| 2456 | * @deprecated Removed in 1.27. |
||
| 2457 | * @return Password |
||
| 2458 | * @since 1.24 |
||
| 2459 | */ |
||
| 2460 | public function getTemporaryPassword() { |
||
| 2463 | |||
| 2464 | /** |
||
| 2465 | * Set the password and reset the random token. |
||
| 2466 | * Calls through to authentication plugin if necessary; |
||
| 2467 | * will have no effect if the auth plugin refuses to |
||
| 2468 | * pass the change through or if the legal password |
||
| 2469 | * checks fail. |
||
| 2470 | * |
||
| 2471 | * As a special case, setting the password to null |
||
| 2472 | * wipes it, so the account cannot be logged in until |
||
| 2473 | * a new password is set, for instance via e-mail. |
||
| 2474 | * |
||
| 2475 | * @deprecated since 1.27, use AuthManager instead |
||
| 2476 | * @param string $str New password to set |
||
| 2477 | * @throws PasswordError On failure |
||
| 2478 | * @return bool |
||
| 2479 | */ |
||
| 2480 | public function setPassword( $str ) { |
||
| 2483 | |||
| 2484 | /** |
||
| 2485 | * Set the password and reset the random token unconditionally. |
||
| 2486 | * |
||
| 2487 | * @deprecated since 1.27, use AuthManager instead |
||
| 2488 | * @param string|null $str New password to set or null to set an invalid |
||
| 2489 | * password hash meaning that the user will not be able to log in |
||
| 2490 | * through the web interface. |
||
| 2491 | */ |
||
| 2492 | public function setInternalPassword( $str ) { |
||
| 2495 | |||
| 2496 | /** |
||
| 2497 | * Actually set the password and such |
||
| 2498 | * @since 1.27 cannot set a password for a user not in the database |
||
| 2499 | * @param string|null $str New password to set or null to set an invalid |
||
| 2500 | * password hash meaning that the user will not be able to log in |
||
| 2501 | * through the web interface. |
||
| 2502 | * @return bool Success |
||
| 2503 | */ |
||
| 2504 | private function setPasswordInternal( $str ) { |
||
| 2529 | |||
| 2530 | /** |
||
| 2531 | * Changes credentials of the user. |
||
| 2532 | * |
||
| 2533 | * This is a convenience wrapper around AuthManager::changeAuthenticationData. |
||
| 2534 | * Note that this can return a status that isOK() but not isGood() on certain types of failures, |
||
| 2535 | * e.g. when no provider handled the change. |
||
| 2536 | * |
||
| 2537 | * @param array $data A set of authentication data in fieldname => value format. This is the |
||
| 2538 | * same data you would pass the changeauthenticationdata API - 'username', 'password' etc. |
||
| 2539 | * @return Status |
||
| 2540 | * @since 1.27 |
||
| 2541 | */ |
||
| 2542 | public function changeAuthenticationData( array $data ) { |
||
| 2562 | |||
| 2563 | /** |
||
| 2564 | * Get the user's current token. |
||
| 2565 | * @param bool $forceCreation Force the generation of a new token if the |
||
| 2566 | * user doesn't have one (default=true for backwards compatibility). |
||
| 2567 | * @return string|null Token |
||
| 2568 | */ |
||
| 2569 | public function getToken( $forceCreation = true ) { |
||
| 2600 | |||
| 2601 | /** |
||
| 2602 | * Set the random token (used for persistent authentication) |
||
| 2603 | * Called from loadDefaults() among other places. |
||
| 2604 | * |
||
| 2605 | * @param string|bool $token If specified, set the token to this value |
||
| 2606 | */ |
||
| 2607 | public function setToken( $token = false ) { |
||
| 2618 | |||
| 2619 | /** |
||
| 2620 | * Set the password for a password reminder or new account email |
||
| 2621 | * |
||
| 2622 | * @deprecated Removed in 1.27. Use PasswordReset instead. |
||
| 2623 | * @param string $str New password to set or null to set an invalid |
||
| 2624 | * password hash meaning that the user will not be able to use it |
||
| 2625 | * @param bool $throttle If true, reset the throttle timestamp to the present |
||
| 2626 | */ |
||
| 2627 | public function setNewpassword( $str, $throttle = true ) { |
||
| 2630 | |||
| 2631 | /** |
||
| 2632 | * Has password reminder email been sent within the last |
||
| 2633 | * $wgPasswordReminderResendTime hours? |
||
| 2634 | * @deprecated Removed in 1.27. See above. |
||
| 2635 | * @return bool |
||
| 2636 | */ |
||
| 2637 | public function isPasswordReminderThrottled() { |
||
| 2640 | |||
| 2641 | /** |
||
| 2642 | * Get the user's e-mail address |
||
| 2643 | * @return string User's email address |
||
| 2644 | */ |
||
| 2645 | public function getEmail() { |
||
| 2650 | |||
| 2651 | /** |
||
| 2652 | * Get the timestamp of the user's e-mail authentication |
||
| 2653 | * @return string TS_MW timestamp |
||
| 2654 | */ |
||
| 2655 | public function getEmailAuthenticationTimestamp() { |
||
| 2660 | |||
| 2661 | /** |
||
| 2662 | * Set the user's e-mail address |
||
| 2663 | * @param string $str New e-mail address |
||
| 2664 | */ |
||
| 2665 | public function setEmail( $str ) { |
||
| 2674 | |||
| 2675 | /** |
||
| 2676 | * Set the user's e-mail address and a confirmation mail if needed. |
||
| 2677 | * |
||
| 2678 | * @since 1.20 |
||
| 2679 | * @param string $str New e-mail address |
||
| 2680 | * @return Status |
||
| 2681 | */ |
||
| 2682 | public function setEmailWithConfirmation( $str ) { |
||
| 2732 | |||
| 2733 | /** |
||
| 2734 | * Get the user's real name |
||
| 2735 | * @return string User's real name |
||
| 2736 | */ |
||
| 2737 | public function getRealName() { |
||
| 2744 | |||
| 2745 | /** |
||
| 2746 | * Set the user's real name |
||
| 2747 | * @param string $str New real name |
||
| 2748 | */ |
||
| 2749 | public function setRealName( $str ) { |
||
| 2753 | |||
| 2754 | /** |
||
| 2755 | * Get the user's current setting for a given option. |
||
| 2756 | * |
||
| 2757 | * @param string $oname The option to check |
||
| 2758 | * @param string $defaultOverride A default value returned if the option does not exist |
||
| 2759 | * @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs |
||
| 2760 | * @return string User's current value for the option |
||
| 2761 | * @see getBoolOption() |
||
| 2762 | * @see getIntOption() |
||
| 2763 | */ |
||
| 2764 | public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) { |
||
| 2783 | |||
| 2784 | /** |
||
| 2785 | * Get all user's options |
||
| 2786 | * |
||
| 2787 | * @param int $flags Bitwise combination of: |
||
| 2788 | * User::GETOPTIONS_EXCLUDE_DEFAULTS Exclude user options that are set |
||
| 2789 | * to the default value. (Since 1.25) |
||
| 2790 | * @return array |
||
| 2791 | */ |
||
| 2792 | public function getOptions( $flags = 0 ) { |
||
| 2815 | |||
| 2816 | /** |
||
| 2817 | * Get the user's current setting for a given option, as a boolean value. |
||
| 2818 | * |
||
| 2819 | * @param string $oname The option to check |
||
| 2820 | * @return bool User's current value for the option |
||
| 2821 | * @see getOption() |
||
| 2822 | */ |
||
| 2823 | public function getBoolOption( $oname ) { |
||
| 2826 | |||
| 2827 | /** |
||
| 2828 | * Get the user's current setting for a given option, as an integer value. |
||
| 2829 | * |
||
| 2830 | * @param string $oname The option to check |
||
| 2831 | * @param int $defaultOverride A default value returned if the option does not exist |
||
| 2832 | * @return int User's current value for the option |
||
| 2833 | * @see getOption() |
||
| 2834 | */ |
||
| 2835 | public function getIntOption( $oname, $defaultOverride = 0 ) { |
||
| 2842 | |||
| 2843 | /** |
||
| 2844 | * Set the given option for a user. |
||
| 2845 | * |
||
| 2846 | * You need to call saveSettings() to actually write to the database. |
||
| 2847 | * |
||
| 2848 | * @param string $oname The option to set |
||
| 2849 | * @param mixed $val New value to set |
||
| 2850 | */ |
||
| 2851 | public function setOption( $oname, $val ) { |
||
| 2861 | |||
| 2862 | /** |
||
| 2863 | * Get a token stored in the preferences (like the watchlist one), |
||
| 2864 | * resetting it if it's empty (and saving changes). |
||
| 2865 | * |
||
| 2866 | * @param string $oname The option name to retrieve the token from |
||
| 2867 | * @return string|bool User's current value for the option, or false if this option is disabled. |
||
| 2868 | * @see resetTokenFromOption() |
||
| 2869 | * @see getOption() |
||
| 2870 | * @deprecated since 1.26 Applications should use the OAuth extension |
||
| 2871 | */ |
||
| 2872 | public function getTokenFromOption( $oname ) { |
||
| 2890 | |||
| 2891 | /** |
||
| 2892 | * Reset a token stored in the preferences (like the watchlist one). |
||
| 2893 | * *Does not* save user's preferences (similarly to setOption()). |
||
| 2894 | * |
||
| 2895 | * @param string $oname The option name to reset the token in |
||
| 2896 | * @return string|bool New token value, or false if this option is disabled. |
||
| 2897 | * @see getTokenFromOption() |
||
| 2898 | * @see setOption() |
||
| 2899 | */ |
||
| 2900 | public function resetTokenFromOption( $oname ) { |
||
| 2910 | |||
| 2911 | /** |
||
| 2912 | * Return a list of the types of user options currently returned by |
||
| 2913 | * User::getOptionKinds(). |
||
| 2914 | * |
||
| 2915 | * Currently, the option kinds are: |
||
| 2916 | * - 'registered' - preferences which are registered in core MediaWiki or |
||
| 2917 | * by extensions using the UserGetDefaultOptions hook. |
||
| 2918 | * - 'registered-multiselect' - as above, using the 'multiselect' type. |
||
| 2919 | * - 'registered-checkmatrix' - as above, using the 'checkmatrix' type. |
||
| 2920 | * - 'userjs' - preferences with names starting with 'userjs-', intended to |
||
| 2921 | * be used by user scripts. |
||
| 2922 | * - 'special' - "preferences" that are not accessible via User::getOptions |
||
| 2923 | * or User::setOptions. |
||
| 2924 | * - 'unused' - preferences about which MediaWiki doesn't know anything. |
||
| 2925 | * These are usually legacy options, removed in newer versions. |
||
| 2926 | * |
||
| 2927 | * The API (and possibly others) use this function to determine the possible |
||
| 2928 | * option types for validation purposes, so make sure to update this when a |
||
| 2929 | * new option kind is added. |
||
| 2930 | * |
||
| 2931 | * @see User::getOptionKinds |
||
| 2932 | * @return array Option kinds |
||
| 2933 | */ |
||
| 2934 | public static function listOptionKinds() { |
||
| 2944 | |||
| 2945 | /** |
||
| 2946 | * Return an associative array mapping preferences keys to the kind of a preference they're |
||
| 2947 | * used for. Different kinds are handled differently when setting or reading preferences. |
||
| 2948 | * |
||
| 2949 | * See User::listOptionKinds for the list of valid option types that can be provided. |
||
| 2950 | * |
||
| 2951 | * @see User::listOptionKinds |
||
| 2952 | * @param IContextSource $context |
||
| 2953 | * @param array $options Assoc. array with options keys to check as keys. |
||
| 2954 | * Defaults to $this->mOptions. |
||
| 2955 | * @return array The key => kind mapping data |
||
| 2956 | */ |
||
| 2957 | public function getOptionKinds( IContextSource $context, $options = null ) { |
||
| 3026 | |||
| 3027 | /** |
||
| 3028 | * Reset certain (or all) options to the site defaults |
||
| 3029 | * |
||
| 3030 | * The optional parameter determines which kinds of preferences will be reset. |
||
| 3031 | * Supported values are everything that can be reported by getOptionKinds() |
||
| 3032 | * and 'all', which forces a reset of *all* preferences and overrides everything else. |
||
| 3033 | * |
||
| 3034 | * @param array|string $resetKinds Which kinds of preferences to reset. Defaults to |
||
| 3035 | * array( 'registered', 'registered-multiselect', 'registered-checkmatrix', 'unused' ) |
||
| 3036 | * for backwards-compatibility. |
||
| 3037 | * @param IContextSource|null $context Context source used when $resetKinds |
||
| 3038 | * does not contain 'all', passed to getOptionKinds(). |
||
| 3039 | * Defaults to RequestContext::getMain() when null. |
||
| 3040 | */ |
||
| 3041 | public function resetOptions( |
||
| 3081 | |||
| 3082 | /** |
||
| 3083 | * Get the user's preferred date format. |
||
| 3084 | * @return string User's preferred date format |
||
| 3085 | */ |
||
| 3086 | public function getDatePreference() { |
||
| 3099 | |||
| 3100 | /** |
||
| 3101 | * Determine based on the wiki configuration and the user's options, |
||
| 3102 | * whether this user must be over HTTPS no matter what. |
||
| 3103 | * |
||
| 3104 | * @return bool |
||
| 3105 | */ |
||
| 3106 | public function requiresHTTPS() { |
||
| 3119 | |||
| 3120 | /** |
||
| 3121 | * Get the user preferred stub threshold |
||
| 3122 | * |
||
| 3123 | * @return int |
||
| 3124 | */ |
||
| 3125 | public function getStubThreshold() { |
||
| 3135 | |||
| 3136 | /** |
||
| 3137 | * Get the permissions this user has. |
||
| 3138 | * @return array Array of String permission names |
||
| 3139 | */ |
||
| 3140 | public function getRights() { |
||
| 3175 | |||
| 3176 | /** |
||
| 3177 | * Get the list of explicit group memberships this user has. |
||
| 3178 | * The implicit * and user groups are not included. |
||
| 3179 | * @return array Array of String internal group names |
||
| 3180 | */ |
||
| 3181 | public function getGroups() { |
||
| 3186 | |||
| 3187 | /** |
||
| 3188 | * Get the list of implicit group memberships this user has. |
||
| 3189 | * This includes all explicit groups, plus 'user' if logged in, |
||
| 3190 | * '*' for all accounts, and autopromoted groups |
||
| 3191 | * @param bool $recache Whether to avoid the cache |
||
| 3192 | * @return array Array of String internal group names |
||
| 3193 | */ |
||
| 3194 | public function getEffectiveGroups( $recache = false ) { |
||
| 3207 | |||
| 3208 | /** |
||
| 3209 | * Get the list of implicit group memberships this user has. |
||
| 3210 | * This includes 'user' if logged in, '*' for all accounts, |
||
| 3211 | * and autopromoted groups |
||
| 3212 | * @param bool $recache Whether to avoid the cache |
||
| 3213 | * @return array Array of String internal group names |
||
| 3214 | */ |
||
| 3215 | public function getAutomaticGroups( $recache = false ) { |
||
| 3234 | |||
| 3235 | /** |
||
| 3236 | * Returns the groups the user has belonged to. |
||
| 3237 | * |
||
| 3238 | * The user may still belong to the returned groups. Compare with getGroups(). |
||
| 3239 | * |
||
| 3240 | * The function will not return groups the user had belonged to before MW 1.17 |
||
| 3241 | * |
||
| 3242 | * @return array Names of the groups the user has belonged to. |
||
| 3243 | */ |
||
| 3244 | View Code Duplication | public function getFormerGroups() { |
|
| 3263 | |||
| 3264 | /** |
||
| 3265 | * Get the user's edit count. |
||
| 3266 | * @return int|null Null for anonymous users |
||
| 3267 | */ |
||
| 3268 | public function getEditCount() { |
||
| 3291 | |||
| 3292 | /** |
||
| 3293 | * Add the user to the given group. |
||
| 3294 | * This takes immediate effect. |
||
| 3295 | * @param string $group Name of the group to add |
||
| 3296 | * @return bool |
||
| 3297 | */ |
||
| 3298 | public function addGroup( $group ) { |
||
| 3331 | |||
| 3332 | /** |
||
| 3333 | * Remove the user from the given group. |
||
| 3334 | * This takes immediate effect. |
||
| 3335 | * @param string $group Name of the group to remove |
||
| 3336 | * @return bool |
||
| 3337 | */ |
||
| 3338 | public function removeGroup( $group ) { |
||
| 3373 | |||
| 3374 | /** |
||
| 3375 | * Get whether the user is logged in |
||
| 3376 | * @return bool |
||
| 3377 | */ |
||
| 3378 | public function isLoggedIn() { |
||
| 3381 | |||
| 3382 | /** |
||
| 3383 | * Get whether the user is anonymous |
||
| 3384 | * @return bool |
||
| 3385 | */ |
||
| 3386 | public function isAnon() { |
||
| 3389 | |||
| 3390 | /** |
||
| 3391 | * @return bool Whether this user is flagged as being a bot role account |
||
| 3392 | * @since 1.28 |
||
| 3393 | */ |
||
| 3394 | public function isBot() { |
||
| 3404 | |||
| 3405 | /** |
||
| 3406 | * Check if user is allowed to access a feature / make an action |
||
| 3407 | * |
||
| 3408 | * @param string ... Permissions to test |
||
| 3409 | * @return bool True if user is allowed to perform *any* of the given actions |
||
| 3410 | */ |
||
| 3411 | View Code Duplication | public function isAllowedAny() { |
|
| 3420 | |||
| 3421 | /** |
||
| 3422 | * |
||
| 3423 | * @param string ... Permissions to test |
||
| 3424 | * @return bool True if the user is allowed to perform *all* of the given actions |
||
| 3425 | */ |
||
| 3426 | View Code Duplication | public function isAllowedAll() { |
|
| 3435 | |||
| 3436 | /** |
||
| 3437 | * Internal mechanics of testing a permission |
||
| 3438 | * @param string $action |
||
| 3439 | * @return bool |
||
| 3440 | */ |
||
| 3441 | public function isAllowed( $action = '' ) { |
||
| 3449 | |||
| 3450 | /** |
||
| 3451 | * Check whether to enable recent changes patrol features for this user |
||
| 3452 | * @return bool True or false |
||
| 3453 | */ |
||
| 3454 | public function useRCPatrol() { |
||
| 3458 | |||
| 3459 | /** |
||
| 3460 | * Check whether to enable new pages patrol features for this user |
||
| 3461 | * @return bool True or false |
||
| 3462 | */ |
||
| 3463 | public function useNPPatrol() { |
||
| 3470 | |||
| 3471 | /** |
||
| 3472 | * Check whether to enable new files patrol features for this user |
||
| 3473 | * @return bool True or false |
||
| 3474 | */ |
||
| 3475 | public function useFilePatrol() { |
||
| 3482 | |||
| 3483 | /** |
||
| 3484 | * Get the WebRequest object to use with this object |
||
| 3485 | * |
||
| 3486 | * @return WebRequest |
||
| 3487 | */ |
||
| 3488 | public function getRequest() { |
||
| 3496 | |||
| 3497 | /** |
||
| 3498 | * Check the watched status of an article. |
||
| 3499 | * @since 1.22 $checkRights parameter added |
||
| 3500 | * @param Title $title Title of the article to look at |
||
| 3501 | * @param bool $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights. |
||
| 3502 | * Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS. |
||
| 3503 | * @return bool |
||
| 3504 | */ |
||
| 3505 | public function isWatched( $title, $checkRights = self::CHECK_USER_RIGHTS ) { |
||
| 3511 | |||
| 3512 | /** |
||
| 3513 | * Watch an article. |
||
| 3514 | * @since 1.22 $checkRights parameter added |
||
| 3515 | * @param Title $title Title of the article to look at |
||
| 3516 | * @param bool $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights. |
||
| 3517 | * Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS. |
||
| 3518 | */ |
||
| 3519 | public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) { |
||
| 3528 | |||
| 3529 | /** |
||
| 3530 | * Stop watching an article. |
||
| 3531 | * @since 1.22 $checkRights parameter added |
||
| 3532 | * @param Title $title Title of the article to look at |
||
| 3533 | * @param bool $checkRights Whether to check 'viewmywatchlist'/'editmywatchlist' rights. |
||
| 3534 | * Pass User::CHECK_USER_RIGHTS or User::IGNORE_USER_RIGHTS. |
||
| 3535 | */ |
||
| 3536 | public function removeWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) { |
||
| 3544 | |||
| 3545 | /** |
||
| 3546 | * Clear the user's notification timestamp for the given title. |
||
| 3547 | * If e-notif e-mails are on, they will receive notification mails on |
||
| 3548 | * the next change of the page if it's watched etc. |
||
| 3549 | * @note If the user doesn't have 'editmywatchlist', this will do nothing. |
||
| 3550 | * @param Title $title Title of the article to look at |
||
| 3551 | * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed. |
||
| 3552 | */ |
||
| 3553 | public function clearNotification( &$title, $oldid = 0 ) { |
||
| 3612 | |||
| 3613 | /** |
||
| 3614 | * Resets all of the given user's page-change notification timestamps. |
||
| 3615 | * If e-notif e-mails are on, they will receive notification mails on |
||
| 3616 | * the next change of any watched page. |
||
| 3617 | * @note If the user doesn't have 'editmywatchlist', this will do nothing. |
||
| 3618 | */ |
||
| 3619 | public function clearAllNotifications() { |
||
| 3684 | |||
| 3685 | /** |
||
| 3686 | * Set a cookie on the user's client. Wrapper for |
||
| 3687 | * WebResponse::setCookie |
||
| 3688 | * @deprecated since 1.27 |
||
| 3689 | * @param string $name Name of the cookie to set |
||
| 3690 | * @param string $value Value to set |
||
| 3691 | * @param int $exp Expiration time, as a UNIX time value; |
||
| 3692 | * if 0 or not specified, use the default $wgCookieExpiration |
||
| 3693 | * @param bool $secure |
||
| 3694 | * true: Force setting the secure attribute when setting the cookie |
||
| 3695 | * false: Force NOT setting the secure attribute when setting the cookie |
||
| 3696 | * null (default): Use the default ($wgCookieSecure) to set the secure attribute |
||
| 3697 | * @param array $params Array of options sent passed to WebResponse::setcookie() |
||
| 3698 | * @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null |
||
| 3699 | * is passed. |
||
| 3700 | */ |
||
| 3701 | protected function setCookie( |
||
| 3711 | |||
| 3712 | /** |
||
| 3713 | * Clear a cookie on the user's client |
||
| 3714 | * @deprecated since 1.27 |
||
| 3715 | * @param string $name Name of the cookie to clear |
||
| 3716 | * @param bool $secure |
||
| 3717 | * true: Force setting the secure attribute when setting the cookie |
||
| 3718 | * false: Force NOT setting the secure attribute when setting the cookie |
||
| 3719 | * null (default): Use the default ($wgCookieSecure) to set the secure attribute |
||
| 3720 | * @param array $params Array of options sent passed to WebResponse::setcookie() |
||
| 3721 | */ |
||
| 3722 | protected function clearCookie( $name, $secure = null, $params = [] ) { |
||
| 3726 | |||
| 3727 | /** |
||
| 3728 | * Set an extended login cookie on the user's client. The expiry of the cookie |
||
| 3729 | * is controlled by the $wgExtendedLoginCookieExpiration configuration |
||
| 3730 | * variable. |
||
| 3731 | * |
||
| 3732 | * @see User::setCookie |
||
| 3733 | * |
||
| 3734 | * @deprecated since 1.27 |
||
| 3735 | * @param string $name Name of the cookie to set |
||
| 3736 | * @param string $value Value to set |
||
| 3737 | * @param bool $secure |
||
| 3738 | * true: Force setting the secure attribute when setting the cookie |
||
| 3739 | * false: Force NOT setting the secure attribute when setting the cookie |
||
| 3740 | * null (default): Use the default ($wgCookieSecure) to set the secure attribute |
||
| 3741 | */ |
||
| 3742 | protected function setExtendedLoginCookie( $name, $value, $secure ) { |
||
| 3754 | |||
| 3755 | /** |
||
| 3756 | * Persist this user's session (e.g. set cookies) |
||
| 3757 | * |
||
| 3758 | * @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null |
||
| 3759 | * is passed. |
||
| 3760 | * @param bool $secure Whether to force secure/insecure cookies or use default |
||
| 3761 | * @param bool $rememberMe Whether to add a Token cookie for elongated sessions |
||
| 3762 | */ |
||
| 3763 | public function setCookies( $request = null, $secure = null, $rememberMe = false ) { |
||
| 3795 | |||
| 3796 | /** |
||
| 3797 | * Log this user out. |
||
| 3798 | */ |
||
| 3799 | public function logout() { |
||
| 3800 | if ( Hooks::run( 'UserLogout', [ &$this ] ) ) { |
||
| 3801 | $this->doLogout(); |
||
| 3802 | } |
||
| 3803 | } |
||
| 3804 | |||
| 3805 | /** |
||
| 3806 | * Clear the user's session, and reset the instance cache. |
||
| 3807 | * @see logout() |
||
| 3808 | */ |
||
| 3809 | public function doLogout() { |
||
| 3840 | |||
| 3841 | /** |
||
| 3842 | * Save this user's settings into the database. |
||
| 3843 | * @todo Only rarely do all these fields need to be set! |
||
| 3844 | */ |
||
| 3845 | public function saveSettings() { |
||
| 3900 | |||
| 3901 | /** |
||
| 3902 | * If only this user's username is known, and it exists, return the user ID. |
||
| 3903 | * |
||
| 3904 | * @param int $flags Bitfield of User:READ_* constants; useful for existence checks |
||
| 3905 | * @return int |
||
| 3906 | */ |
||
| 3907 | public function idForName( $flags = 0 ) { |
||
| 3926 | |||
| 3927 | /** |
||
| 3928 | * Add a user to the database, return the user object |
||
| 3929 | * |
||
| 3930 | * @param string $name Username to add |
||
| 3931 | * @param array $params Array of Strings Non-default parameters to save to |
||
| 3932 | * the database as user_* fields: |
||
| 3933 | * - email: The user's email address. |
||
| 3934 | * - email_authenticated: The email authentication timestamp. |
||
| 3935 | * - real_name: The user's real name. |
||
| 3936 | * - options: An associative array of non-default options. |
||
| 3937 | * - token: Random authentication token. Do not set. |
||
| 3938 | * - registration: Registration timestamp. Do not set. |
||
| 3939 | * |
||
| 3940 | * @return User|null User object, or null if the username already exists. |
||
| 3941 | */ |
||
| 3942 | public static function createNew( $name, $params = [] ) { |
||
| 3986 | |||
| 3987 | /** |
||
| 3988 | * Add this existing user object to the database. If the user already |
||
| 3989 | * exists, a fatal status object is returned, and the user object is |
||
| 3990 | * initialised with the data from the database. |
||
| 3991 | * |
||
| 3992 | * Previously, this function generated a DB error due to a key conflict |
||
| 3993 | * if the user already existed. Many extension callers use this function |
||
| 3994 | * in code along the lines of: |
||
| 3995 | * |
||
| 3996 | * $user = User::newFromName( $name ); |
||
| 3997 | * if ( !$user->isLoggedIn() ) { |
||
| 3998 | * $user->addToDatabase(); |
||
| 3999 | * } |
||
| 4000 | * // do something with $user... |
||
| 4001 | * |
||
| 4002 | * However, this was vulnerable to a race condition (bug 16020). By |
||
| 4003 | * initialising the user object if the user exists, we aim to support this |
||
| 4004 | * calling sequence as far as possible. |
||
| 4005 | * |
||
| 4006 | * Note that if the user exists, this function will acquire a write lock, |
||
| 4007 | * so it is still advisable to make the call conditional on isLoggedIn(), |
||
| 4008 | * and to commit the transaction after calling. |
||
| 4009 | * |
||
| 4010 | * @throws MWException |
||
| 4011 | * @return Status |
||
| 4012 | */ |
||
| 4013 | public function addToDatabase() { |
||
| 4071 | |||
| 4072 | /** |
||
| 4073 | * If this user is logged-in and blocked, |
||
| 4074 | * block any IP address they've successfully logged in from. |
||
| 4075 | * @return bool A block was spread |
||
| 4076 | */ |
||
| 4077 | public function spreadAnyEditBlock() { |
||
| 4084 | |||
| 4085 | /** |
||
| 4086 | * If this (non-anonymous) user is blocked, |
||
| 4087 | * block the IP address they've successfully logged in from. |
||
| 4088 | * @return bool A block was spread |
||
| 4089 | */ |
||
| 4090 | protected function spreadBlock() { |
||
| 4104 | |||
| 4105 | /** |
||
| 4106 | * Get whether the user is explicitly blocked from account creation. |
||
| 4107 | * @return bool|Block |
||
| 4108 | */ |
||
| 4109 | public function isBlockedFromCreateAccount() { |
||
| 4126 | |||
| 4127 | /** |
||
| 4128 | * Get whether the user is blocked from using Special:Emailuser. |
||
| 4129 | * @return bool |
||
| 4130 | */ |
||
| 4131 | public function isBlockedFromEmailuser() { |
||
| 4135 | |||
| 4136 | /** |
||
| 4137 | * Get whether the user is allowed to create an account. |
||
| 4138 | * @return bool |
||
| 4139 | */ |
||
| 4140 | public function isAllowedToCreateAccount() { |
||
| 4143 | |||
| 4144 | /** |
||
| 4145 | * Get this user's personal page title. |
||
| 4146 | * |
||
| 4147 | * @return Title User's personal page title |
||
| 4148 | */ |
||
| 4149 | public function getUserPage() { |
||
| 4152 | |||
| 4153 | /** |
||
| 4154 | * Get this user's talk page title. |
||
| 4155 | * |
||
| 4156 | * @return Title User's talk page title |
||
| 4157 | */ |
||
| 4158 | public function getTalkPage() { |
||
| 4162 | |||
| 4163 | /** |
||
| 4164 | * Determine whether the user is a newbie. Newbies are either |
||
| 4165 | * anonymous IPs, or the most recently created accounts. |
||
| 4166 | * @return bool |
||
| 4167 | */ |
||
| 4168 | public function isNewbie() { |
||
| 4171 | |||
| 4172 | /** |
||
| 4173 | * Check to see if the given clear-text password is one of the accepted passwords |
||
| 4174 | * @deprecated since 1.27, use AuthManager instead |
||
| 4175 | * @param string $password User password |
||
| 4176 | * @return bool True if the given password is correct, otherwise False |
||
| 4177 | */ |
||
| 4178 | public function checkPassword( $password ) { |
||
| 4202 | |||
| 4203 | /** |
||
| 4204 | * Check if the given clear-text password matches the temporary password |
||
| 4205 | * sent by e-mail for password reset operations. |
||
| 4206 | * |
||
| 4207 | * @deprecated since 1.27, use AuthManager instead |
||
| 4208 | * @param string $plaintext |
||
| 4209 | * @return bool True if matches, false otherwise |
||
| 4210 | */ |
||
| 4211 | public function checkTemporaryPassword( $plaintext ) { |
||
| 4215 | |||
| 4216 | /** |
||
| 4217 | * Initialize (if necessary) and return a session token value |
||
| 4218 | * which can be used in edit forms to show that the user's |
||
| 4219 | * login credentials aren't being hijacked with a foreign form |
||
| 4220 | * submission. |
||
| 4221 | * |
||
| 4222 | * @since 1.27 |
||
| 4223 | * @param string|array $salt Array of Strings Optional function-specific data for hashing |
||
| 4224 | * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest |
||
| 4225 | * @return MediaWiki\Session\Token The new edit token |
||
| 4226 | */ |
||
| 4227 | public function getEditTokenObject( $salt = '', $request = null ) { |
||
| 4237 | |||
| 4238 | /** |
||
| 4239 | * Initialize (if necessary) and return a session token value |
||
| 4240 | * which can be used in edit forms to show that the user's |
||
| 4241 | * login credentials aren't being hijacked with a foreign form |
||
| 4242 | * submission. |
||
| 4243 | * |
||
| 4244 | * The $salt for 'edit' and 'csrf' tokens is the default (empty string). |
||
| 4245 | * |
||
| 4246 | * @since 1.19 |
||
| 4247 | * @param string|array $salt Array of Strings Optional function-specific data for hashing |
||
| 4248 | * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest |
||
| 4249 | * @return string The new edit token |
||
| 4250 | */ |
||
| 4251 | public function getEditToken( $salt = '', $request = null ) { |
||
| 4254 | |||
| 4255 | /** |
||
| 4256 | * Get the embedded timestamp from a token. |
||
| 4257 | * @deprecated since 1.27, use \MediaWiki\Session\Token::getTimestamp instead. |
||
| 4258 | * @param string $val Input token |
||
| 4259 | * @return int|null |
||
| 4260 | */ |
||
| 4261 | public static function getEditTokenTimestamp( $val ) { |
||
| 4265 | |||
| 4266 | /** |
||
| 4267 | * Check given value against the token value stored in the session. |
||
| 4268 | * A match should confirm that the form was submitted from the |
||
| 4269 | * user's own login session, not a form submission from a third-party |
||
| 4270 | * site. |
||
| 4271 | * |
||
| 4272 | * @param string $val Input value to compare |
||
| 4273 | * @param string $salt Optional function-specific data for hashing |
||
| 4274 | * @param WebRequest|null $request Object to use or null to use $wgRequest |
||
| 4275 | * @param int $maxage Fail tokens older than this, in seconds |
||
| 4276 | * @return bool Whether the token matches |
||
| 4277 | */ |
||
| 4278 | public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) { |
||
| 4281 | |||
| 4282 | /** |
||
| 4283 | * Check given value against the token value stored in the session, |
||
| 4284 | * ignoring the suffix. |
||
| 4285 | * |
||
| 4286 | * @param string $val Input value to compare |
||
| 4287 | * @param string $salt Optional function-specific data for hashing |
||
| 4288 | * @param WebRequest|null $request Object to use or null to use $wgRequest |
||
| 4289 | * @param int $maxage Fail tokens older than this, in seconds |
||
| 4290 | * @return bool Whether the token matches |
||
| 4291 | */ |
||
| 4292 | public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) { |
||
| 4296 | |||
| 4297 | /** |
||
| 4298 | * Generate a new e-mail confirmation token and send a confirmation/invalidation |
||
| 4299 | * mail to the user's given address. |
||
| 4300 | * |
||
| 4301 | * @param string $type Message to send, either "created", "changed" or "set" |
||
| 4302 | * @return Status |
||
| 4303 | */ |
||
| 4304 | public function sendConfirmationMail( $type = 'created' ) { |
||
| 4331 | |||
| 4332 | /** |
||
| 4333 | * Send an e-mail to this user's account. Does not check for |
||
| 4334 | * confirmed status or validity. |
||
| 4335 | * |
||
| 4336 | * @param string $subject Message subject |
||
| 4337 | * @param string $body Message body |
||
| 4338 | * @param User|null $from Optional sending user; if unspecified, default |
||
| 4339 | * $wgPasswordSender will be used. |
||
| 4340 | * @param string $replyto Reply-To address |
||
| 4341 | * @return Status |
||
| 4342 | */ |
||
| 4343 | public function sendMail( $subject, $body, $from = null, $replyto = null ) { |
||
| 4358 | |||
| 4359 | /** |
||
| 4360 | * Generate, store, and return a new e-mail confirmation code. |
||
| 4361 | * A hash (unsalted, since it's used as a key) is stored. |
||
| 4362 | * |
||
| 4363 | * @note Call saveSettings() after calling this function to commit |
||
| 4364 | * this change to the database. |
||
| 4365 | * |
||
| 4366 | * @param string &$expiration Accepts the expiration time |
||
| 4367 | * @return string New token |
||
| 4368 | */ |
||
| 4369 | protected function confirmationToken( &$expiration ) { |
||
| 4381 | |||
| 4382 | /** |
||
| 4383 | * Return a URL the user can use to confirm their email address. |
||
| 4384 | * @param string $token Accepts the email confirmation token |
||
| 4385 | * @return string New token URL |
||
| 4386 | */ |
||
| 4387 | protected function confirmationTokenUrl( $token ) { |
||
| 4390 | |||
| 4391 | /** |
||
| 4392 | * Return a URL the user can use to invalidate their email address. |
||
| 4393 | * @param string $token Accepts the email confirmation token |
||
| 4394 | * @return string New token URL |
||
| 4395 | */ |
||
| 4396 | protected function invalidationTokenUrl( $token ) { |
||
| 4399 | |||
| 4400 | /** |
||
| 4401 | * Internal function to format the e-mail validation/invalidation URLs. |
||
| 4402 | * This uses a quickie hack to use the |
||
| 4403 | * hardcoded English names of the Special: pages, for ASCII safety. |
||
| 4404 | * |
||
| 4405 | * @note Since these URLs get dropped directly into emails, using the |
||
| 4406 | * short English names avoids insanely long URL-encoded links, which |
||
| 4407 | * also sometimes can get corrupted in some browsers/mailers |
||
| 4408 | * (bug 6957 with Gmail and Internet Explorer). |
||
| 4409 | * |
||
| 4410 | * @param string $page Special page |
||
| 4411 | * @param string $token Token |
||
| 4412 | * @return string Formatted URL |
||
| 4413 | */ |
||
| 4414 | protected function getTokenUrl( $page, $token ) { |
||
| 4419 | |||
| 4420 | /** |
||
| 4421 | * Mark the e-mail address confirmed. |
||
| 4422 | * |
||
| 4423 | * @note Call saveSettings() after calling this function to commit the change. |
||
| 4424 | * |
||
| 4425 | * @return bool |
||
| 4426 | */ |
||
| 4427 | public function confirmEmail() { |
||
| 4436 | |||
| 4437 | /** |
||
| 4438 | * Invalidate the user's e-mail confirmation, and unauthenticate the e-mail |
||
| 4439 | * address if it was already confirmed. |
||
| 4440 | * |
||
| 4441 | * @note Call saveSettings() after calling this function to commit the change. |
||
| 4442 | * @return bool Returns true |
||
| 4443 | */ |
||
| 4444 | public function invalidateEmail() { |
||
| 4453 | |||
| 4454 | /** |
||
| 4455 | * Set the e-mail authentication timestamp. |
||
| 4456 | * @param string $timestamp TS_MW timestamp |
||
| 4457 | */ |
||
| 4458 | public function setEmailAuthenticationTimestamp( $timestamp ) { |
||
| 4463 | |||
| 4464 | /** |
||
| 4465 | * Is this user allowed to send e-mails within limits of current |
||
| 4466 | * site configuration? |
||
| 4467 | * @return bool |
||
| 4468 | */ |
||
| 4469 | public function canSendEmail() { |
||
| 4478 | |||
| 4479 | /** |
||
| 4480 | * Is this user allowed to receive e-mails within limits of current |
||
| 4481 | * site configuration? |
||
| 4482 | * @return bool |
||
| 4483 | */ |
||
| 4484 | public function canReceiveEmail() { |
||
| 4487 | |||
| 4488 | /** |
||
| 4489 | * Is this user's e-mail address valid-looking and confirmed within |
||
| 4490 | * limits of the current site configuration? |
||
| 4491 | * |
||
| 4492 | * @note If $wgEmailAuthentication is on, this may require the user to have |
||
| 4493 | * confirmed their address by returning a code or using a password |
||
| 4494 | * sent to the address from the wiki. |
||
| 4495 | * |
||
| 4496 | * @return bool |
||
| 4497 | */ |
||
| 4498 | public function isEmailConfirmed() { |
||
| 4517 | |||
| 4518 | /** |
||
| 4519 | * Check whether there is an outstanding request for e-mail confirmation. |
||
| 4520 | * @return bool |
||
| 4521 | */ |
||
| 4522 | public function isEmailConfirmationPending() { |
||
| 4529 | |||
| 4530 | /** |
||
| 4531 | * Get the timestamp of account creation. |
||
| 4532 | * |
||
| 4533 | * @return string|bool|null Timestamp of account creation, false for |
||
| 4534 | * non-existent/anonymous user accounts, or null if existing account |
||
| 4535 | * but information is not in database. |
||
| 4536 | */ |
||
| 4537 | public function getRegistration() { |
||
| 4544 | |||
| 4545 | /** |
||
| 4546 | * Get the timestamp of the first edit |
||
| 4547 | * |
||
| 4548 | * @return string|bool Timestamp of first edit, or false for |
||
| 4549 | * non-existent/anonymous user accounts. |
||
| 4550 | */ |
||
| 4551 | public function getFirstEditTimestamp() { |
||
| 4566 | |||
| 4567 | /** |
||
| 4568 | * Get the permissions associated with a given list of groups |
||
| 4569 | * |
||
| 4570 | * @param array $groups Array of Strings List of internal group names |
||
| 4571 | * @return array Array of Strings List of permission key names for given groups combined |
||
| 4572 | */ |
||
| 4573 | public static function getGroupPermissions( $groups ) { |
||
| 4593 | |||
| 4594 | /** |
||
| 4595 | * Get all the groups who have a given permission |
||
| 4596 | * |
||
| 4597 | * @param string $role Role to check |
||
| 4598 | * @return array Array of Strings List of internal group names with the given permission |
||
| 4599 | */ |
||
| 4600 | public static function getGroupsWithPermission( $role ) { |
||
| 4610 | |||
| 4611 | /** |
||
| 4612 | * Check, if the given group has the given permission |
||
| 4613 | * |
||
| 4614 | * If you're wanting to check whether all users have a permission, use |
||
| 4615 | * User::isEveryoneAllowed() instead. That properly checks if it's revoked |
||
| 4616 | * from anyone. |
||
| 4617 | * |
||
| 4618 | * @since 1.21 |
||
| 4619 | * @param string $group Group to check |
||
| 4620 | * @param string $role Role to check |
||
| 4621 | * @return bool |
||
| 4622 | */ |
||
| 4623 | public static function groupHasPermission( $group, $role ) { |
||
| 4628 | |||
| 4629 | /** |
||
| 4630 | * Check if all users may be assumed to have the given permission |
||
| 4631 | * |
||
| 4632 | * We generally assume so if the right is granted to '*' and isn't revoked |
||
| 4633 | * on any group. It doesn't attempt to take grants or other extension |
||
| 4634 | * limitations on rights into account in the general case, though, as that |
||
| 4635 | * would require it to always return false and defeat the purpose. |
||
| 4636 | * Specifically, session-based rights restrictions (such as OAuth or bot |
||
| 4637 | * passwords) are applied based on the current session. |
||
| 4638 | * |
||
| 4639 | * @since 1.22 |
||
| 4640 | * @param string $right Right to check |
||
| 4641 | * @return bool |
||
| 4642 | */ |
||
| 4643 | public static function isEveryoneAllowed( $right ) { |
||
| 4685 | |||
| 4686 | /** |
||
| 4687 | * Get the localized descriptive name for a group, if it exists |
||
| 4688 | * |
||
| 4689 | * @param string $group Internal group name |
||
| 4690 | * @return string Localized descriptive group name |
||
| 4691 | */ |
||
| 4692 | public static function getGroupName( $group ) { |
||
| 4696 | |||
| 4697 | /** |
||
| 4698 | * Get the localized descriptive name for a member of a group, if it exists |
||
| 4699 | * |
||
| 4700 | * @param string $group Internal group name |
||
| 4701 | * @param string $username Username for gender (since 1.19) |
||
| 4702 | * @return string Localized name for group member |
||
| 4703 | */ |
||
| 4704 | public static function getGroupMember( $group, $username = '#' ) { |
||
| 4708 | |||
| 4709 | /** |
||
| 4710 | * Return the set of defined explicit groups. |
||
| 4711 | * The implicit groups (by default *, 'user' and 'autoconfirmed') |
||
| 4712 | * are not included, as they are defined automatically, not in the database. |
||
| 4713 | * @return array Array of internal group names |
||
| 4714 | */ |
||
| 4715 | public static function getAllGroups() { |
||
| 4722 | |||
| 4723 | /** |
||
| 4724 | * Get a list of all available permissions. |
||
| 4725 | * @return string[] Array of permission names |
||
| 4726 | */ |
||
| 4727 | public static function getAllRights() { |
||
| 4739 | |||
| 4740 | /** |
||
| 4741 | * Get a list of implicit groups |
||
| 4742 | * @return array Array of Strings Array of internal group names |
||
| 4743 | */ |
||
| 4744 | public static function getImplicitGroups() { |
||
| 4753 | |||
| 4754 | /** |
||
| 4755 | * Get the title of a page describing a particular group |
||
| 4756 | * |
||
| 4757 | * @param string $group Internal group name |
||
| 4758 | * @return Title|bool Title of the page if it exists, false otherwise |
||
| 4759 | */ |
||
| 4760 | public static function getGroupPage( $group ) { |
||
| 4770 | |||
| 4771 | /** |
||
| 4772 | * Create a link to the group in HTML, if available; |
||
| 4773 | * else return the group name. |
||
| 4774 | * |
||
| 4775 | * @param string $group Internal name of the group |
||
| 4776 | * @param string $text The text of the link |
||
| 4777 | * @return string HTML link to the group |
||
| 4778 | */ |
||
| 4779 | public static function makeGroupLinkHTML( $group, $text = '' ) { |
||
| 4790 | |||
| 4791 | /** |
||
| 4792 | * Create a link to the group in Wikitext, if available; |
||
| 4793 | * else return the group name. |
||
| 4794 | * |
||
| 4795 | * @param string $group Internal name of the group |
||
| 4796 | * @param string $text The text of the link |
||
| 4797 | * @return string Wikilink to the group |
||
| 4798 | */ |
||
| 4799 | public static function makeGroupLinkWiki( $group, $text = '' ) { |
||
| 4811 | |||
| 4812 | /** |
||
| 4813 | * Returns an array of the groups that a particular group can add/remove. |
||
| 4814 | * |
||
| 4815 | * @param string $group The group to check for whether it can add/remove |
||
| 4816 | * @return array Array( 'add' => array( addablegroups ), |
||
| 4817 | * 'remove' => array( removablegroups ), |
||
| 4818 | * 'add-self' => array( addablegroups to self), |
||
| 4819 | * 'remove-self' => array( removable groups from self) ) |
||
| 4820 | */ |
||
| 4821 | public static function changeableByGroup( $group ) { |
||
| 4886 | |||
| 4887 | /** |
||
| 4888 | * Returns an array of groups that this user can add and remove |
||
| 4889 | * @return array Array( 'add' => array( addablegroups ), |
||
| 4890 | * 'remove' => array( removablegroups ), |
||
| 4891 | * 'add-self' => array( addablegroups to self), |
||
| 4892 | * 'remove-self' => array( removable groups from self) ) |
||
| 4893 | */ |
||
| 4894 | public function changeableGroups() { |
||
| 4929 | |||
| 4930 | /** |
||
| 4931 | * Deferred version of incEditCountImmediate() |
||
| 4932 | */ |
||
| 4933 | public function incEditCount() { |
||
| 4941 | |||
| 4942 | /** |
||
| 4943 | * Increment the user's edit-count field. |
||
| 4944 | * Will have no effect for anonymous users. |
||
| 4945 | * @since 1.26 |
||
| 4946 | */ |
||
| 4947 | public function incEditCountImmediate() { |
||
| 4987 | |||
| 4988 | /** |
||
| 4989 | * Initialize user_editcount from data out of the revision table |
||
| 4990 | * |
||
| 4991 | * @param int $add Edits to add to the count from the revision table |
||
| 4992 | * @return int Number of edits |
||
| 4993 | */ |
||
| 4994 | protected function initEditCount( $add = 0 ) { |
||
| 5016 | |||
| 5017 | /** |
||
| 5018 | * Get the description of a given right |
||
| 5019 | * |
||
| 5020 | * @param string $right Right to query |
||
| 5021 | * @return string Localized description of the right |
||
| 5022 | */ |
||
| 5023 | public static function getRightDescription( $right ) { |
||
| 5028 | |||
| 5029 | /** |
||
| 5030 | * Make a new-style password hash |
||
| 5031 | * |
||
| 5032 | * @param string $password Plain-text password |
||
| 5033 | * @param bool|string $salt Optional salt, may be random or the user ID. |
||
| 5034 | * If unspecified or false, will generate one automatically |
||
| 5035 | * @return string Password hash |
||
| 5036 | * @deprecated since 1.24, use Password class |
||
| 5037 | */ |
||
| 5038 | public static function crypt( $password, $salt = false ) { |
||
| 5045 | |||
| 5046 | /** |
||
| 5047 | * Compare a password hash with a plain-text password. Requires the user |
||
| 5048 | * ID if there's a chance that the hash is an old-style hash. |
||
| 5049 | * |
||
| 5050 | * @param string $hash Password hash |
||
| 5051 | * @param string $password Plain-text password to compare |
||
| 5052 | * @param string|bool $userId User ID for old-style password salt |
||
| 5053 | * |
||
| 5054 | * @return bool |
||
| 5055 | * @deprecated since 1.24, use Password class |
||
| 5056 | */ |
||
| 5057 | public static function comparePasswords( $hash, $password, $userId = false ) { |
||
| 5076 | |||
| 5077 | /** |
||
| 5078 | * Add a newuser log entry for this user. |
||
| 5079 | * Before 1.19 the return value was always true. |
||
| 5080 | * |
||
| 5081 | * @deprecated since 1.27, AuthManager handles logging |
||
| 5082 | * @param string|bool $action Account creation type. |
||
| 5083 | * - String, one of the following values: |
||
| 5084 | * - 'create' for an anonymous user creating an account for himself. |
||
| 5085 | * This will force the action's performer to be the created user itself, |
||
| 5086 | * no matter the value of $wgUser |
||
| 5087 | * - 'create2' for a logged in user creating an account for someone else |
||
| 5088 | * - 'byemail' when the created user will receive its password by e-mail |
||
| 5089 | * - 'autocreate' when the user is automatically created (such as by CentralAuth). |
||
| 5090 | * - Boolean means whether the account was created by e-mail (deprecated): |
||
| 5091 | * - true will be converted to 'byemail' |
||
| 5092 | * - false will be converted to 'create' if this object is the same as |
||
| 5093 | * $wgUser and to 'create2' otherwise |
||
| 5094 | * @param string $reason User supplied reason |
||
| 5095 | * @return bool true |
||
| 5096 | */ |
||
| 5097 | public function addNewUserLogEntry( $action = false, $reason = '' ) { |
||
| 5100 | |||
| 5101 | /** |
||
| 5102 | * Add an autocreate newuser log entry for this user |
||
| 5103 | * Used by things like CentralAuth and perhaps other authplugins. |
||
| 5104 | * Consider calling addNewUserLogEntry() directly instead. |
||
| 5105 | * |
||
| 5106 | * @deprecated since 1.27, AuthManager handles logging |
||
| 5107 | * @return bool |
||
| 5108 | */ |
||
| 5109 | public function addNewUserLogEntryAutoCreate() { |
||
| 5114 | |||
| 5115 | /** |
||
| 5116 | * Load the user options either from cache, the database or an array |
||
| 5117 | * |
||
| 5118 | * @param array $data Rows for the current user out of the user_properties table |
||
| 5119 | */ |
||
| 5120 | protected function loadOptions( $data = null ) { |
||
| 5180 | |||
| 5181 | /** |
||
| 5182 | * Saves the non-default options for this user, as previously set e.g. via |
||
| 5183 | * setOption(), in the database's "user_properties" (preferences) table. |
||
| 5184 | * Usually used via saveSettings(). |
||
| 5185 | */ |
||
| 5186 | protected function saveOptions() { |
||
| 5245 | |||
| 5246 | /** |
||
| 5247 | * Lazily instantiate and return a factory object for making passwords |
||
| 5248 | * |
||
| 5249 | * @deprecated since 1.27, create a PasswordFactory directly instead |
||
| 5250 | * @return PasswordFactory |
||
| 5251 | */ |
||
| 5252 | public static function getPasswordFactory() { |
||
| 5258 | |||
| 5259 | /** |
||
| 5260 | * Provide an array of HTML5 attributes to put on an input element |
||
| 5261 | * intended for the user to enter a new password. This may include |
||
| 5262 | * required, title, and/or pattern, depending on $wgMinimalPasswordLength. |
||
| 5263 | * |
||
| 5264 | * Do *not* use this when asking the user to enter his current password! |
||
| 5265 | * Regardless of configuration, users may have invalid passwords for whatever |
||
| 5266 | * reason (e.g., they were set before requirements were tightened up). |
||
| 5267 | * Only use it when asking for a new password, like on account creation or |
||
| 5268 | * ResetPass. |
||
| 5269 | * |
||
| 5270 | * Obviously, you still need to do server-side checking. |
||
| 5271 | * |
||
| 5272 | * NOTE: A combination of bugs in various browsers means that this function |
||
| 5273 | * actually just returns array() unconditionally at the moment. May as |
||
| 5274 | * well keep it around for when the browser bugs get fixed, though. |
||
| 5275 | * |
||
| 5276 | * @todo FIXME: This does not belong here; put it in Html or Linker or somewhere |
||
| 5277 | * |
||
| 5278 | * @deprecated since 1.27 |
||
| 5279 | * @return array Array of HTML attributes suitable for feeding to |
||
| 5280 | * Html::element(), directly or indirectly. (Don't feed to Xml::*()! |
||
| 5281 | * That will get confused by the boolean attribute syntax used.) |
||
| 5282 | */ |
||
| 5283 | public static function passwordChangeInputAttribs() { |
||
| 5317 | |||
| 5318 | /** |
||
| 5319 | * Return the list of user fields that should be selected to create |
||
| 5320 | * a new user object. |
||
| 5321 | * @return array |
||
| 5322 | */ |
||
| 5323 | public static function selectFields() { |
||
| 5338 | |||
| 5339 | /** |
||
| 5340 | * Factory function for fatal permission-denied errors |
||
| 5341 | * |
||
| 5342 | * @since 1.22 |
||
| 5343 | * @param string $permission User right required |
||
| 5344 | * @return Status |
||
| 5345 | */ |
||
| 5346 | static function newFatalPermissionDeniedStatus( $permission ) { |
||
| 5360 | |||
| 5361 | /** |
||
| 5362 | * Get a new instance of this user that was loaded from the master via a locking read |
||
| 5363 | * |
||
| 5364 | * Use this instead of the main context User when updating that user. This avoids races |
||
| 5365 | * where that user was loaded from a replica DB or even the master but without proper locks. |
||
| 5366 | * |
||
| 5367 | * @return User|null Returns null if the user was not found in the DB |
||
| 5368 | * @since 1.27 |
||
| 5369 | */ |
||
| 5370 | public function getInstanceForUpdate() { |
||
| 5382 | |||
| 5383 | /** |
||
| 5384 | * Checks if two user objects point to the same user. |
||
| 5385 | * |
||
| 5386 | * @since 1.25 |
||
| 5387 | * @param User $user |
||
| 5388 | * @return bool |
||
| 5389 | */ |
||
| 5390 | public function equals( User $user ) { |
||
| 5393 | } |
||
| 5394 |
Let’s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let’s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: