| 1 | <?php |
|---|
| 2 | /** |
|---|
| 3 | * Implements the User class for the %MediaWiki software. |
|---|
| 4 | * @file |
|---|
| 5 | */ |
|---|
| 6 | |
|---|
| 7 | /** |
|---|
| 8 | * \int Number of characters in user_token field. |
|---|
| 9 | * @ingroup Constants |
|---|
| 10 | */ |
|---|
| 11 | define( 'USER_TOKEN_LENGTH', 32 ); |
|---|
| 12 | |
|---|
| 13 | /** |
|---|
| 14 | * \int Serialized record version. |
|---|
| 15 | * @ingroup Constants |
|---|
| 16 | */ |
|---|
| 17 | define( 'MW_USER_VERSION', 8 ); |
|---|
| 18 | |
|---|
| 19 | /** |
|---|
| 20 | * \string Some punctuation to prevent editing from broken text-mangling proxies. |
|---|
| 21 | * @ingroup Constants |
|---|
| 22 | */ |
|---|
| 23 | define( 'EDIT_TOKEN_SUFFIX', '+\\' ); |
|---|
| 24 | |
|---|
| 25 | /** |
|---|
| 26 | * Thrown by User::setPassword() on error. |
|---|
| 27 | * @ingroup Exception |
|---|
| 28 | */ |
|---|
| 29 | class PasswordError extends MWException { |
|---|
| 30 | // NOP |
|---|
| 31 | } |
|---|
| 32 | |
|---|
| 33 | /** |
|---|
| 34 | * The User object encapsulates all of the user-specific settings (user_id, |
|---|
| 35 | * name, rights, password, email address, options, last login time). Client |
|---|
| 36 | * classes use the getXXX() functions to access these fields. These functions |
|---|
| 37 | * do all the work of determining whether the user is logged in, |
|---|
| 38 | * whether the requested option can be satisfied from cookies or |
|---|
| 39 | * whether a database query is needed. Most of the settings needed |
|---|
| 40 | * for rendering normal pages are set in the cookie to minimize use |
|---|
| 41 | * of the database. |
|---|
| 42 | */ |
|---|
| 43 | class User { |
|---|
| 44 | |
|---|
| 45 | /** |
|---|
| 46 | * \type{\arrayof{\string}} A list of default user toggles, i.e., boolean user |
|---|
| 47 | * preferences that are displayed by Special:Preferences as checkboxes. |
|---|
| 48 | * This list can be extended via the UserToggles hook or by |
|---|
| 49 | * $wgContLang::getExtraUserToggles(). |
|---|
| 50 | * @showinitializer |
|---|
| 51 | */ |
|---|
| 52 | public static $mToggles = array( |
|---|
| 53 | 'highlightbroken', |
|---|
| 54 | 'justify', |
|---|
| 55 | 'hideminor', |
|---|
| 56 | 'extendwatchlist', |
|---|
| 57 | 'usenewrc', |
|---|
| 58 | 'numberheadings', |
|---|
| 59 | 'showtoolbar', |
|---|
| 60 | 'editondblclick', |
|---|
| 61 | 'editsection', |
|---|
| 62 | 'editsectiononrightclick', |
|---|
| 63 | 'showtoc', |
|---|
| 64 | 'rememberpassword', |
|---|
| 65 | 'editwidth', |
|---|
| 66 | 'watchcreations', |
|---|
| 67 | 'watchdefault', |
|---|
| 68 | 'watchmoves', |
|---|
| 69 | 'watchdeletion', |
|---|
| 70 | 'minordefault', |
|---|
| 71 | 'previewontop', |
|---|
| 72 | 'previewonfirst', |
|---|
| 73 | 'nocache', |
|---|
| 74 | 'enotifwatchlistpages', |
|---|
| 75 | 'enotifusertalkpages', |
|---|
| 76 | 'enotifminoredits', |
|---|
| 77 | 'enotifrevealaddr', |
|---|
| 78 | 'shownumberswatching', |
|---|
| 79 | 'fancysig', |
|---|
| 80 | 'externaleditor', |
|---|
| 81 | 'externaldiff', |
|---|
| 82 | 'showjumplinks', |
|---|
| 83 | 'uselivepreview', |
|---|
| 84 | 'forceeditsummary', |
|---|
| 85 | 'watchlisthideminor', |
|---|
| 86 | 'watchlisthidebots', |
|---|
| 87 | 'watchlisthideown', |
|---|
| 88 | 'watchlisthideanons', |
|---|
| 89 | 'watchlisthideliu', |
|---|
| 90 | 'ccmeonemails', |
|---|
| 91 | 'diffonly', |
|---|
| 92 | 'showhiddencats', |
|---|
| 93 | 'noconvertlink', |
|---|
| 94 | 'norollbackdiff', |
|---|
| 95 | ); |
|---|
| 96 | |
|---|
| 97 | /** |
|---|
| 98 | * \type{\arrayof{\string}} List of member variables which are saved to the |
|---|
| 99 | * shared cache (memcached). Any operation which changes the |
|---|
| 100 | * corresponding database fields must call a cache-clearing function. |
|---|
| 101 | * @showinitializer |
|---|
| 102 | */ |
|---|
| 103 | static $mCacheVars = array( |
|---|
| 104 | // user table |
|---|
| 105 | 'mId', |
|---|
| 106 | 'mName', |
|---|
| 107 | 'mRealName', |
|---|
| 108 | 'mPassword', |
|---|
| 109 | 'mNewpassword', |
|---|
| 110 | 'mNewpassTime', |
|---|
| 111 | 'mEmail', |
|---|
| 112 | 'mTouched', |
|---|
| 113 | 'mToken', |
|---|
| 114 | 'mEmailAuthenticated', |
|---|
| 115 | 'mEmailToken', |
|---|
| 116 | 'mEmailTokenExpires', |
|---|
| 117 | 'mRegistration', |
|---|
| 118 | 'mEditCount', |
|---|
| 119 | // user_group table |
|---|
| 120 | 'mGroups', |
|---|
| 121 | // user_properties table |
|---|
| 122 | 'mOptionOverrides', |
|---|
| 123 | ); |
|---|
| 124 | |
|---|
| 125 | /** |
|---|
| 126 | * \type{\arrayof{\string}} Core rights. |
|---|
| 127 | * Each of these should have a corresponding message of the form |
|---|
| 128 | * "right-$right". |
|---|
| 129 | * @showinitializer |
|---|
| 130 | */ |
|---|
| 131 | static $mCoreRights = array( |
|---|
| 132 | 'apihighlimits', |
|---|
| 133 | 'autoconfirmed', |
|---|
| 134 | 'autopatrol', |
|---|
| 135 | 'bigdelete', |
|---|
| 136 | 'block', |
|---|
| 137 | 'blockemail', |
|---|
| 138 | 'bot', |
|---|
| 139 | 'browsearchive', |
|---|
| 140 | 'createaccount', |
|---|
| 141 | 'createpage', |
|---|
| 142 | 'createtalk', |
|---|
| 143 | 'delete', |
|---|
| 144 | 'deletedhistory', |
|---|
| 145 | 'deletedtext', |
|---|
| 146 | 'deleterevision', |
|---|
| 147 | 'edit', |
|---|
| 148 | 'editinterface', |
|---|
| 149 | 'editusercssjs', |
|---|
| 150 | 'hideuser', |
|---|
| 151 | 'import', |
|---|
| 152 | 'importupload', |
|---|
| 153 | 'ipblock-exempt', |
|---|
| 154 | 'markbotedits', |
|---|
| 155 | 'minoredit', |
|---|
| 156 | 'move', |
|---|
| 157 | 'movefile', |
|---|
| 158 | 'move-rootuserpages', |
|---|
| 159 | 'move-subpages', |
|---|
| 160 | 'nominornewtalk', |
|---|
| 161 | 'noratelimit', |
|---|
| 162 | 'override-export-depth', |
|---|
| 163 | 'patrol', |
|---|
| 164 | 'protect', |
|---|
| 165 | 'proxyunbannable', |
|---|
| 166 | 'purge', |
|---|
| 167 | 'read', |
|---|
| 168 | 'reupload', |
|---|
| 169 | 'reupload-shared', |
|---|
| 170 | 'rollback', |
|---|
| 171 | 'sendemail', |
|---|
| 172 | 'siteadmin', |
|---|
| 173 | 'suppressionlog', |
|---|
| 174 | 'suppressredirect', |
|---|
| 175 | 'suppressrevision', |
|---|
| 176 | 'trackback', |
|---|
| 177 | 'undelete', |
|---|
| 178 | 'unwatchedpages', |
|---|
| 179 | 'upload', |
|---|
| 180 | 'upload_by_url', |
|---|
| 181 | 'userrights', |
|---|
| 182 | 'userrights-interwiki', |
|---|
| 183 | 'writeapi', |
|---|
| 184 | ); |
|---|
| 185 | /** |
|---|
| 186 | * \string Cached results of getAllRights() |
|---|
| 187 | */ |
|---|
| 188 | static $mAllRights = false; |
|---|
| 189 | |
|---|
| 190 | /** @name Cache variables */ |
|---|
| 191 | //@{ |
|---|
| 192 | var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime, |
|---|
| 193 | $mEmail, $mTouched, $mToken, $mEmailAuthenticated, |
|---|
| 194 | $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups, $mOptionOverrides; |
|---|
| 195 | //@} |
|---|
| 196 | |
|---|
| 197 | /** |
|---|
| 198 | * \bool Whether the cache variables have been loaded. |
|---|
| 199 | */ |
|---|
| 200 | var $mDataLoaded, $mAuthLoaded, $mOptionsLoaded; |
|---|
| 201 | |
|---|
| 202 | /** |
|---|
| 203 | * \string Initialization data source if mDataLoaded==false. May be one of: |
|---|
| 204 | * - 'defaults' anonymous user initialised from class defaults |
|---|
| 205 | * - 'name' initialise from mName |
|---|
| 206 | * - 'id' initialise from mId |
|---|
| 207 | * - 'session' log in from cookies or session if possible |
|---|
| 208 | * |
|---|
| 209 | * Use the User::newFrom*() family of functions to set this. |
|---|
| 210 | */ |
|---|
| 211 | var $mFrom; |
|---|
| 212 | |
|---|
| 213 | /** @name Lazy-initialized variables, invalidated with clearInstanceCache */ |
|---|
| 214 | //@{ |
|---|
| 215 | var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights, |
|---|
| 216 | $mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally, |
|---|
| 217 | $mLocked, $mHideName, $mOptions; |
|---|
| 218 | //@} |
|---|
| 219 | |
|---|
| 220 | static $idCacheByName = array(); |
|---|
| 221 | |
|---|
| 222 | /** |
|---|
| 223 | * Lightweight constructor for an anonymous user. |
|---|
| 224 | * Use the User::newFrom* factory functions for other kinds of users. |
|---|
| 225 | * |
|---|
| 226 | * @see newFromName() |
|---|
| 227 | * @see newFromId() |
|---|
| 228 | * @see newFromConfirmationCode() |
|---|
| 229 | * @see newFromSession() |
|---|
| 230 | * @see newFromRow() |
|---|
| 231 | */ |
|---|
| 232 | function User() { |
|---|
| 233 | $this->clearInstanceCache( 'defaults' ); |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | /** |
|---|
| 237 | * Load the user table data for this object from the source given by mFrom. |
|---|
| 238 | */ |
|---|
| 239 | function load() { |
|---|
| 240 | if ( $this->mDataLoaded ) { |
|---|
| 241 | return; |
|---|
| 242 | } |
|---|
| 243 | wfProfileIn( __METHOD__ ); |
|---|
| 244 | |
|---|
| 245 | # Set it now to avoid infinite recursion in accessors |
|---|
| 246 | $this->mDataLoaded = true; |
|---|
| 247 | |
|---|
| 248 | switch ( $this->mFrom ) { |
|---|
| 249 | case 'defaults': |
|---|
| 250 | $this->loadDefaults(); |
|---|
| 251 | break; |
|---|
| 252 | case 'name': |
|---|
| 253 | $this->mId = self::idFromName( $this->mName ); |
|---|
| 254 | if ( !$this->mId ) { |
|---|
| 255 | # Nonexistent user placeholder object |
|---|
| 256 | $this->loadDefaults( $this->mName ); |
|---|
| 257 | } else { |
|---|
| 258 | $this->loadFromId(); |
|---|
| 259 | } |
|---|
| 260 | break; |
|---|
| 261 | case 'id': |
|---|
| 262 | $this->loadFromId(); |
|---|
| 263 | break; |
|---|
| 264 | case 'session': |
|---|
| 265 | $this->loadFromSession(); |
|---|
| 266 | wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) ); |
|---|
| 267 | break; |
|---|
| 268 | default: |
|---|
| 269 | throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" ); |
|---|
| 270 | } |
|---|
| 271 | wfProfileOut( __METHOD__ ); |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | /** |
|---|
| 275 | * Load user table data, given mId has already been set. |
|---|
| 276 | * @return \bool false if the ID does not exist, true otherwise |
|---|
| 277 | * @private |
|---|
| 278 | */ |
|---|
| 279 | function loadFromId() { |
|---|
| 280 | global $wgMemc; |
|---|
| 281 | if ( $this->mId == 0 ) { |
|---|
| 282 | $this->loadDefaults(); |
|---|
| 283 | return false; |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | # Try cache |
|---|
| 287 | $key = wfMemcKey( 'user', 'id', $this->mId ); |
|---|
| 288 | $data = $wgMemc->get( $key ); |
|---|
| 289 | if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) { |
|---|
| 290 | # Object is expired, load from DB |
|---|
| 291 | $data = false; |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | if ( !$data ) { |
|---|
| 295 | wfDebug( "Cache miss for user {$this->mId}\n" ); |
|---|
| 296 | # Load from DB |
|---|
| 297 | if ( !$this->loadFromDatabase() ) { |
|---|
| 298 | # Can't load from ID, user is anonymous |
|---|
| 299 | return false; |
|---|
| 300 | } |
|---|
| 301 | $this->saveToCache(); |
|---|
| 302 | } else { |
|---|
| 303 | wfDebug( "Got user {$this->mId} from cache\n" ); |
|---|
| 304 | # Restore from cache |
|---|
| 305 | foreach ( self::$mCacheVars as $name ) { |
|---|
| 306 | $this->$name = $data[$name]; |
|---|
| 307 | } |
|---|
| 308 | } |
|---|
| 309 | return true; |
|---|
| 310 | } |
|---|
| 311 | |
|---|
| 312 | /** |
|---|
| 313 | * Save user data to the shared cache |
|---|
| 314 | */ |
|---|
| 315 | function saveToCache() { |
|---|
| 316 | $this->load(); |
|---|
| 317 | $this->loadGroups(); |
|---|
| 318 | $this->loadOptions(); |
|---|
| 319 | if ( $this->isAnon() ) { |
|---|
| 320 | // Anonymous users are uncached |
|---|
| 321 | return; |
|---|
| 322 | } |
|---|
| 323 | $data = array(); |
|---|
| 324 | foreach ( self::$mCacheVars as $name ) { |
|---|
| 325 | $data[$name] = $this->$name; |
|---|
| 326 | } |
|---|
| 327 | $data['mVersion'] = MW_USER_VERSION; |
|---|
| 328 | $key = wfMemcKey( 'user', 'id', $this->mId ); |
|---|
| 329 | global $wgMemc; |
|---|
| 330 | $wgMemc->set( $key, $data ); |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | |
|---|
| 334 | /** @name newFrom*() static factory methods */ |
|---|
| 335 | //@{ |
|---|
| 336 | |
|---|
| 337 | /** |
|---|
| 338 | * Static factory method for creation from username. |
|---|
| 339 | * |
|---|
| 340 | * This is slightly less efficient than newFromId(), so use newFromId() if |
|---|
| 341 | * you have both an ID and a name handy. |
|---|
| 342 | * |
|---|
| 343 | * @param $name \string Username, validated by Title::newFromText() |
|---|
| 344 | * @param $validate \mixed Validate username. Takes the same parameters as |
|---|
| 345 | * User::getCanonicalName(), except that true is accepted as an alias |
|---|
| 346 | * for 'valid', for BC. |
|---|
| 347 | * |
|---|
| 348 | * @return \type{User} The User object, or false if the username is invalid |
|---|
| 349 | * (e.g. if it contains illegal characters or is an IP address). If the |
|---|
| 350 | * username is not present in the database, the result will be a user object |
|---|
| 351 | * with a name, zero user ID and default settings. |
|---|
| 352 | */ |
|---|
| 353 | static function newFromName( $name, $validate = 'valid' ) { |
|---|
| 354 | if ( $validate === true ) { |
|---|
| 355 | $validate = 'valid'; |
|---|
| 356 | } |
|---|
| 357 | $name = self::getCanonicalName( $name, $validate ); |
|---|
| 358 | if ( $name === false ) { |
|---|
| 359 | return false; |
|---|
| 360 | } else { |
|---|
| 361 | # Create unloaded user object |
|---|
| 362 | $u = new User; |
|---|
| 363 | $u->mName = $name; |
|---|
| 364 | $u->mFrom = 'name'; |
|---|
| 365 | return $u; |
|---|
| 366 | } |
|---|
| 367 | } |
|---|
| 368 | |
|---|
| 369 | /** |
|---|
| 370 | * Static factory method for creation from a given user ID. |
|---|
| 371 | * |
|---|
| 372 | * @param $id \int Valid user ID |
|---|
| 373 | * @return \type{User} The corresponding User object |
|---|
| 374 | */ |
|---|
| 375 | static function newFromId( $id ) { |
|---|
| 376 | $u = new User; |
|---|
| 377 | $u->mId = $id; |
|---|
| 378 | $u->mFrom = 'id'; |
|---|
| 379 | return $u; |
|---|
| 380 | } |
|---|
| 381 | |
|---|
| 382 | /** |
|---|
| 383 | * Factory method to fetch whichever user has a given email confirmation code. |
|---|
| 384 | * This code is generated when an account is created or its e-mail address |
|---|
| 385 | * has changed. |
|---|
| 386 | * |
|---|
| 387 | * If the code is invalid or has expired, returns NULL. |
|---|
| 388 | * |
|---|
| 389 | * @param $code \string Confirmation code |
|---|
| 390 | * @return \type{User} |
|---|
| 391 | */ |
|---|
| 392 | static function newFromConfirmationCode( $code ) { |
|---|
| 393 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 394 | $id = $dbr->selectField( 'user', 'user_id', array( |
|---|
| 395 | 'user_email_token' => md5( $code ), |
|---|
| 396 | 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ), |
|---|
| 397 | ) ); |
|---|
| 398 | if( $id !== false ) { |
|---|
| 399 | return User::newFromId( $id ); |
|---|
| 400 | } else { |
|---|
| 401 | return null; |
|---|
| 402 | } |
|---|
| 403 | } |
|---|
| 404 | |
|---|
| 405 | /** |
|---|
| 406 | * Create a new user object using data from session or cookies. If the |
|---|
| 407 | * login credentials are invalid, the result is an anonymous user. |
|---|
| 408 | * |
|---|
| 409 | * @return \type{User} |
|---|
| 410 | */ |
|---|
| 411 | static function newFromSession() { |
|---|
| 412 | $user = new User; |
|---|
| 413 | $user->mFrom = 'session'; |
|---|
| 414 | return $user; |
|---|
| 415 | } |
|---|
| 416 | |
|---|
| 417 | /** |
|---|
| 418 | * Create a new user object from a user row. |
|---|
| 419 | * The row should have all fields from the user table in it. |
|---|
| 420 | * @param $row array A row from the user table |
|---|
| 421 | * @return \type{User} |
|---|
| 422 | */ |
|---|
| 423 | static function newFromRow( $row ) { |
|---|
| 424 | $user = new User; |
|---|
| 425 | $user->loadFromRow( $row ); |
|---|
| 426 | return $user; |
|---|
| 427 | } |
|---|
| 428 | |
|---|
| 429 | //@} |
|---|
| 430 | |
|---|
| 431 | |
|---|
| 432 | /** |
|---|
| 433 | * Get the username corresponding to a given user ID |
|---|
| 434 | * @param $id \int User ID |
|---|
| 435 | * @return \string The corresponding username |
|---|
| 436 | */ |
|---|
| 437 | static function whoIs( $id ) { |
|---|
| 438 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 439 | return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), __METHOD__ ); |
|---|
| 440 | } |
|---|
| 441 | |
|---|
| 442 | /** |
|---|
| 443 | * Get the real name of a user given their user ID |
|---|
| 444 | * |
|---|
| 445 | * @param $id \int User ID |
|---|
| 446 | * @return \string The corresponding user's real name |
|---|
| 447 | */ |
|---|
| 448 | static function whoIsReal( $id ) { |
|---|
| 449 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 450 | return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), __METHOD__ ); |
|---|
| 451 | } |
|---|
| 452 | |
|---|
| 453 | /** |
|---|
| 454 | * Get database id given a user name |
|---|
| 455 | * @param $name \string Username |
|---|
| 456 | * @return \types{\int,\null} The corresponding user's ID, or null if user is nonexistent |
|---|
| 457 | */ |
|---|
| 458 | static function idFromName( $name ) { |
|---|
| 459 | $nt = Title::makeTitleSafe( NS_USER, $name ); |
|---|
| 460 | if( is_null( $nt ) ) { |
|---|
| 461 | # Illegal name |
|---|
| 462 | return null; |
|---|
| 463 | } |
|---|
| 464 | |
|---|
| 465 | if ( isset( self::$idCacheByName[$name] ) ) { |
|---|
| 466 | return self::$idCacheByName[$name]; |
|---|
| 467 | } |
|---|
| 468 | |
|---|
| 469 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 470 | $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), __METHOD__ ); |
|---|
| 471 | |
|---|
| 472 | if ( $s === false ) { |
|---|
| 473 | $result = null; |
|---|
| 474 | } else { |
|---|
| 475 | $result = $s->user_id; |
|---|
| 476 | } |
|---|
| 477 | |
|---|
| 478 | self::$idCacheByName[$name] = $result; |
|---|
| 479 | |
|---|
| 480 | if ( count( self::$idCacheByName ) > 1000 ) { |
|---|
| 481 | self::$idCacheByName = array(); |
|---|
| 482 | } |
|---|
| 483 | |
|---|
| 484 | return $result; |
|---|
| 485 | } |
|---|
| 486 | |
|---|
| 487 | /** |
|---|
| 488 | * Does the string match an anonymous IPv4 address? |
|---|
| 489 | * |
|---|
| 490 | * This function exists for username validation, in order to reject |
|---|
| 491 | * usernames which are similar in form to IP addresses. Strings such |
|---|
| 492 | * as 300.300.300.300 will return true because it looks like an IP |
|---|
| 493 | * address, despite not being strictly valid. |
|---|
| 494 | * |
|---|
| 495 | * We match \d{1,3}\.\d{1,3}\.\d{1,3}\.xxx as an anonymous IP |
|---|
| 496 | * address because the usemod software would "cloak" anonymous IP |
|---|
| 497 | * addresses like this, if we allowed accounts like this to be created |
|---|
| 498 | * new users could get the old edits of these anonymous users. |
|---|
| 499 | * |
|---|
| 500 | * @param $name \string String to match |
|---|
| 501 | * @return \bool True or false |
|---|
| 502 | */ |
|---|
| 503 | static function isIP( $name ) { |
|---|
| 504 | return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name) || IP::isIPv6($name); |
|---|
| 505 | } |
|---|
| 506 | |
|---|
| 507 | /** |
|---|
| 508 | * Is the input a valid username? |
|---|
| 509 | * |
|---|
| 510 | * Checks if the input is a valid username, we don't want an empty string, |
|---|
| 511 | * an IP address, anything that containins slashes (would mess up subpages), |
|---|
| 512 | * is longer than the maximum allowed username size or doesn't begin with |
|---|
| 513 | * a capital letter. |
|---|
| 514 | * |
|---|
| 515 | * @param $name \string String to match |
|---|
| 516 | * @return \bool True or false |
|---|
| 517 | */ |
|---|
| 518 | static function isValidUserName( $name ) { |
|---|
| 519 | global $wgContLang, $wgMaxNameChars; |
|---|
| 520 | |
|---|
| 521 | if ( $name == '' |
|---|
| 522 | || User::isIP( $name ) |
|---|
| 523 | || strpos( $name, '/' ) !== false |
|---|
| 524 | || strlen( $name ) > $wgMaxNameChars |
|---|
| 525 | || $name != $wgContLang->ucfirst( $name ) ) { |
|---|
| 526 | wfDebugLog( 'username', __METHOD__ . |
|---|
| 527 | ": '$name' invalid due to empty, IP, slash, length, or lowercase" ); |
|---|
| 528 | return false; |
|---|
| 529 | } |
|---|
| 530 | |
|---|
| 531 | // Ensure that the name can't be misresolved as a different title, |
|---|
| 532 | // such as with extra namespace keys at the start. |
|---|
| 533 | $parsed = Title::newFromText( $name ); |
|---|
| 534 | if( is_null( $parsed ) |
|---|
| 535 | || $parsed->getNamespace() |
|---|
| 536 | || strcmp( $name, $parsed->getPrefixedText() ) ) { |
|---|
| 537 | wfDebugLog( 'username', __METHOD__ . |
|---|
| 538 | ": '$name' invalid due to ambiguous prefixes" ); |
|---|
| 539 | return false; |
|---|
| 540 | } |
|---|
| 541 | |
|---|
| 542 | // Check an additional blacklist of troublemaker characters. |
|---|
| 543 | // Should these be merged into the title char list? |
|---|
| 544 | $unicodeBlacklist = '/[' . |
|---|
| 545 | '\x{0080}-\x{009f}' . # iso-8859-1 control chars |
|---|
| 546 | '\x{00a0}' . # non-breaking space |
|---|
| 547 | '\x{2000}-\x{200f}' . # various whitespace |
|---|
| 548 | '\x{2028}-\x{202f}' . # breaks and control chars |
|---|
| 549 | '\x{3000}' . # ideographic space |
|---|
| 550 | '\x{e000}-\x{f8ff}' . # private use |
|---|
| 551 | ']/u'; |
|---|
| 552 | if( preg_match( $unicodeBlacklist, $name ) ) { |
|---|
| 553 | wfDebugLog( 'username', __METHOD__ . |
|---|
| 554 | ": '$name' invalid due to blacklisted characters" ); |
|---|
| 555 | return false; |
|---|
| 556 | } |
|---|
| 557 | |
|---|
| 558 | return true; |
|---|
| 559 | } |
|---|
| 560 | |
|---|
| 561 | /** |
|---|
| 562 | * Usernames which fail to pass this function will be blocked |
|---|
| 563 | * from user login and new account registrations, but may be used |
|---|
| 564 | * internally by batch processes. |
|---|
| 565 | * |
|---|
| 566 | * If an account already exists in this form, login will be blocked |
|---|
| 567 | * by a failure to pass this function. |
|---|
| 568 | * |
|---|
| 569 | * @param $name \string String to match |
|---|
| 570 | * @return \bool True or false |
|---|
| 571 | */ |
|---|
| 572 | static function isUsableName( $name ) { |
|---|
| 573 | global $wgReservedUsernames; |
|---|
| 574 | // Must be a valid username, obviously ;) |
|---|
| 575 | if ( !self::isValidUserName( $name ) ) { |
|---|
| 576 | return false; |
|---|
| 577 | } |
|---|
| 578 | |
|---|
| 579 | static $reservedUsernames = false; |
|---|
| 580 | if ( !$reservedUsernames ) { |
|---|
| 581 | $reservedUsernames = $wgReservedUsernames; |
|---|
| 582 | wfRunHooks( 'UserGetReservedNames', array( &$reservedUsernames ) ); |
|---|
| 583 | } |
|---|
| 584 | |
|---|
| 585 | // Certain names may be reserved for batch processes. |
|---|
| 586 | foreach ( $reservedUsernames as $reserved ) { |
|---|
| 587 | if ( substr( $reserved, 0, 4 ) == 'msg:' ) { |
|---|
| 588 | $reserved = wfMsgForContent( substr( $reserved, 4 ) ); |
|---|
| 589 | } |
|---|
| 590 | if ( $reserved == $name ) { |
|---|
| 591 | return false; |
|---|
| 592 | } |
|---|
| 593 | } |
|---|
| 594 | return true; |
|---|
| 595 | } |
|---|
| 596 | |
|---|
| 597 | /** |
|---|
| 598 | * Usernames which fail to pass this function will be blocked |
|---|
| 599 | * from new account registrations, but may be used internally |
|---|
| 600 | * either by batch processes or by user accounts which have |
|---|
| 601 | * already been created. |
|---|
| 602 | * |
|---|
| 603 | * Additional character blacklisting may be added here |
|---|
| 604 | * rather than in isValidUserName() to avoid disrupting |
|---|
| 605 | * existing accounts. |
|---|
| 606 | * |
|---|
| 607 | * @param $name \string String to match |
|---|
| 608 | * @return \bool True or false |
|---|
| 609 | */ |
|---|
| 610 | static function isCreatableName( $name ) { |
|---|
| 611 | global $wgInvalidUsernameCharacters; |
|---|
| 612 | return |
|---|
| 613 | self::isUsableName( $name ) && |
|---|
| 614 | |
|---|
| 615 | // Registration-time character blacklisting... |
|---|
| 616 | !preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ); |
|---|
| 617 | } |
|---|
| 618 | |
|---|
| 619 | /** |
|---|
| 620 | * Is the input a valid password for this user? |
|---|
| 621 | * |
|---|
| 622 | * @param $password String Desired password |
|---|
| 623 | * @return bool True or false |
|---|
| 624 | */ |
|---|
| 625 | function isValidPassword( $password ) { |
|---|
| 626 | //simple boolean wrapper for getPasswordValidity |
|---|
| 627 | return $this->getPasswordValidity( $password ) === true; |
|---|
| 628 | } |
|---|
| 629 | |
|---|
| 630 | /** |
|---|
| 631 | * Given unvalidated password input, return error message on failure. |
|---|
| 632 | * |
|---|
| 633 | * @param $password String Desired password |
|---|
| 634 | * @return mixed: true on success, string of error message on failure |
|---|
| 635 | */ |
|---|
| 636 | function getPasswordValidity( $password ) { |
|---|
| 637 | global $wgMinimalPasswordLength, $wgContLang; |
|---|
| 638 | |
|---|
| 639 | $result = false; //init $result to false for the internal checks |
|---|
| 640 | |
|---|
| 641 | if( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) ) |
|---|
| 642 | return $result; |
|---|
| 643 | |
|---|
| 644 | if ( $result === false ) { |
|---|
| 645 | if( strlen( $password ) < $wgMinimalPasswordLength ) { |
|---|
| 646 | return 'passwordtooshort'; |
|---|
| 647 | } elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) { |
|---|
| 648 | return 'password-name-match'; |
|---|
| 649 | } else { |
|---|
| 650 | //it seems weird returning true here, but this is because of the |
|---|
| 651 | //initialization of $result to false above. If the hook is never run or it |
|---|
| 652 | //doesn't modify $result, then we will likely get down into this if with |
|---|
| 653 | //a valid password. |
|---|
| 654 | return true; |
|---|
| 655 | } |
|---|
| 656 | } elseif( $result === true ) { |
|---|
| 657 | return true; |
|---|
| 658 | } else { |
|---|
| 659 | return $result; //the isValidPassword hook set a string $result and returned true |
|---|
| 660 | } |
|---|
| 661 | } |
|---|
| 662 | |
|---|
| 663 | /** |
|---|
| 664 | * Does a string look like an e-mail address? |
|---|
| 665 | * |
|---|
| 666 | * There used to be a regular expression here, it got removed because it |
|---|
| 667 | * rejected valid addresses. Actually just check if there is '@' somewhere |
|---|
| 668 | * in the given address. |
|---|
| 669 | * |
|---|
| 670 | * @todo Check for RFC 2822 compilance (bug 959) |
|---|
| 671 | * |
|---|
| 672 | * @param $addr \string E-mail address |
|---|
| 673 | * @return \bool True or false |
|---|
| 674 | */ |
|---|
| 675 | public static function isValidEmailAddr( $addr ) { |
|---|
| 676 | $result = null; |
|---|
| 677 | if( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) { |
|---|
| 678 | return $result; |
|---|
| 679 | } |
|---|
| 680 | |
|---|
| 681 | return strpos( $addr, '@' ) !== false; |
|---|
| 682 | } |
|---|
| 683 | |
|---|
| 684 | /** |
|---|
| 685 | * Given unvalidated user input, return a canonical username, or false if |
|---|
| 686 | * the username is invalid. |
|---|
| 687 | * @param $name \string User input |
|---|
| 688 | * @param $validate \types{\string,\bool} Type of validation to use: |
|---|
| 689 | * - false No validation |
|---|
| 690 | * - 'valid' Valid for batch processes |
|---|
| 691 | * - 'usable' Valid for batch processes and login |
|---|
| 692 | * - 'creatable' Valid for batch processes, login and account creation |
|---|
| 693 | */ |
|---|
| 694 | static function getCanonicalName( $name, $validate = 'valid' ) { |
|---|
| 695 | # Force usernames to capital |
|---|
| 696 | global $wgContLang; |
|---|
| 697 | $name = $wgContLang->ucfirst( $name ); |
|---|
| 698 | |
|---|
| 699 | # Reject names containing '#'; these will be cleaned up |
|---|
| 700 | # with title normalisation, but then it's too late to |
|---|
| 701 | # check elsewhere |
|---|
| 702 | if( strpos( $name, '#' ) !== false ) |
|---|
| 703 | return false; |
|---|
| 704 | |
|---|
| 705 | # Clean up name according to title rules |
|---|
| 706 | $t = ( $validate === 'valid' ) ? |
|---|
| 707 | Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name ); |
|---|
| 708 | # Check for invalid titles |
|---|
| 709 | if( is_null( $t ) ) { |
|---|
| 710 | return false; |
|---|
| 711 | } |
|---|
| 712 | |
|---|
| 713 | # Reject various classes of invalid names |
|---|
| 714 | $name = $t->getText(); |
|---|
| 715 | global $wgAuth; |
|---|
| 716 | $name = $wgAuth->getCanonicalName( $t->getText() ); |
|---|
| 717 | |
|---|
| 718 | switch ( $validate ) { |
|---|
| 719 | case false: |
|---|
| 720 | break; |
|---|
| 721 | case 'valid': |
|---|
| 722 | if ( !User::isValidUserName( $name ) ) { |
|---|
| 723 | $name = false; |
|---|
| 724 | } |
|---|
| 725 | break; |
|---|
| 726 | case 'usable': |
|---|
| 727 | if ( !User::isUsableName( $name ) ) { |
|---|
| 728 | $name = false; |
|---|
| 729 | } |
|---|
| 730 | break; |
|---|
| 731 | case 'creatable': |
|---|
| 732 | if ( !User::isCreatableName( $name ) ) { |
|---|
| 733 | $name = false; |
|---|
| 734 | } |
|---|
| 735 | break; |
|---|
| 736 | default: |
|---|
| 737 | throw new MWException( 'Invalid parameter value for $validate in ' . __METHOD__ ); |
|---|
| 738 | } |
|---|
| 739 | return $name; |
|---|
| 740 | } |
|---|
| 741 | |
|---|
| 742 | /** |
|---|
| 743 | * Count the number of edits of a user |
|---|
| 744 | * @todo It should not be static and some day should be merged as proper member function / deprecated -- domas |
|---|
| 745 | * |
|---|
| 746 | * @param $uid \int User ID to check |
|---|
| 747 | * @return \int The user's edit count |
|---|
| 748 | */ |
|---|
| 749 | static function edits( $uid ) { |
|---|
| 750 | wfProfileIn( __METHOD__ ); |
|---|
| 751 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 752 | // check if the user_editcount field has been initialized |
|---|
| 753 | $field = $dbr->selectField( |
|---|
| 754 | 'user', 'user_editcount', |
|---|
| 755 | array( 'user_id' => $uid ), |
|---|
| 756 | __METHOD__ |
|---|
| 757 | ); |
|---|
| 758 | |
|---|
| 759 | if( $field === null ) { // it has not been initialized. do so. |
|---|
| 760 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 761 | $count = $dbr->selectField( |
|---|
| 762 | 'revision', 'count(*)', |
|---|
| 763 | array( 'rev_user' => $uid ), |
|---|
| 764 | __METHOD__ |
|---|
| 765 | ); |
|---|
| 766 | $dbw->update( |
|---|
| 767 | 'user', |
|---|
| 768 | array( 'user_editcount' => $count ), |
|---|
| 769 | array( 'user_id' => $uid ), |
|---|
| 770 | __METHOD__ |
|---|
| 771 | ); |
|---|
| 772 | } else { |
|---|
| 773 | $count = $field; |
|---|
| 774 | } |
|---|
| 775 | wfProfileOut( __METHOD__ ); |
|---|
| 776 | return $count; |
|---|
| 777 | } |
|---|
| 778 | |
|---|
| 779 | /** |
|---|
| 780 | * Return a random password. Sourced from mt_rand, so it's not particularly secure. |
|---|
| 781 | * @todo hash random numbers to improve security, like generateToken() |
|---|
| 782 | * |
|---|
| 783 | * @return \string New random password |
|---|
| 784 | */ |
|---|
| 785 | static function randomPassword() { |
|---|
| 786 | global $wgMinimalPasswordLength; |
|---|
| 787 | $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz'; |
|---|
| 788 | $l = strlen( $pwchars ) - 1; |
|---|
| 789 | |
|---|
| 790 | $pwlength = max( 7, $wgMinimalPasswordLength ); |
|---|
| 791 | $digit = mt_rand( 0, $pwlength - 1 ); |
|---|
| 792 | $np = ''; |
|---|
| 793 | for ( $i = 0; $i < $pwlength; $i++ ) { |
|---|
| 794 | $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars{ mt_rand( 0, $l ) }; |
|---|
| 795 | } |
|---|
| 796 | return $np; |
|---|
| 797 | } |
|---|
| 798 | |
|---|
| 799 | /** |
|---|
| 800 | * Set cached properties to default. |
|---|
| 801 | * |
|---|
| 802 | * @note This no longer clears uncached lazy-initialised properties; |
|---|
| 803 | * the constructor does that instead. |
|---|
| 804 | * @private |
|---|
| 805 | */ |
|---|
| 806 | function loadDefaults( $name = false ) { |
|---|
| 807 | wfProfileIn( __METHOD__ ); |
|---|
| 808 | |
|---|
| 809 | global $wgCookiePrefix; |
|---|
| 810 | |
|---|
| 811 | $this->mId = 0; |
|---|
| 812 | $this->mName = $name; |
|---|
| 813 | $this->mRealName = ''; |
|---|
| 814 | $this->mPassword = $this->mNewpassword = ''; |
|---|
| 815 | $this->mNewpassTime = null; |
|---|
| 816 | $this->mEmail = ''; |
|---|
| 817 | $this->mOptionOverrides = null; |
|---|
| 818 | $this->mOptionsLoaded = false; |
|---|
| 819 | |
|---|
| 820 | if ( isset( $_COOKIE[$wgCookiePrefix.'LoggedOut'] ) ) { |
|---|
| 821 | $this->mTouched = wfTimestamp( TS_MW, $_COOKIE[$wgCookiePrefix.'LoggedOut'] ); |
|---|
| 822 | } else { |
|---|
| 823 | $this->mTouched = '0'; # Allow any pages to be cached |
|---|
| 824 | } |
|---|
| 825 | |
|---|
| 826 | $this->setToken(); # Random |
|---|
| 827 | $this->mEmailAuthenticated = null; |
|---|
| 828 | $this->mEmailToken = ''; |
|---|
| 829 | $this->mEmailTokenExpires = null; |
|---|
| 830 | $this->mRegistration = wfTimestamp( TS_MW ); |
|---|
| 831 | $this->mGroups = array(); |
|---|
| 832 | |
|---|
| 833 | wfRunHooks( 'UserLoadDefaults', array( $this, $name ) ); |
|---|
| 834 | |
|---|
| 835 | wfProfileOut( __METHOD__ ); |
|---|
| 836 | } |
|---|
| 837 | |
|---|
| 838 | /** |
|---|
| 839 | * @deprecated Use wfSetupSession(). |
|---|
| 840 | */ |
|---|
| 841 | function SetupSession() { |
|---|
| 842 | wfDeprecated( __METHOD__ ); |
|---|
| 843 | wfSetupSession(); |
|---|
| 844 | } |
|---|
| 845 | |
|---|
| 846 | /** |
|---|
| 847 | * Load user data from the session or login cookie. If there are no valid |
|---|
| 848 | * credentials, initialises the user as an anonymous user. |
|---|
| 849 | * @return \bool True if the user is logged in, false otherwise. |
|---|
| 850 | */ |
|---|
| 851 | private function loadFromSession() { |
|---|
| 852 | global $wgMemc, $wgCookiePrefix, $wgExternalAuthType, $wgAutocreatePolicy; |
|---|
| 853 | |
|---|
| 854 | $result = null; |
|---|
| 855 | wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) ); |
|---|
| 856 | if ( $result !== null ) { |
|---|
| 857 | return $result; |
|---|
| 858 | } |
|---|
| 859 | |
|---|
| 860 | if ( $wgExternalAuthType && $wgAutocreatePolicy == 'view' ) { |
|---|
| 861 | $extUser = ExternalUser::newFromCookie(); |
|---|
| 862 | if ( $extUser ) { |
|---|
| 863 | # TODO: Automatically create the user here (or probably a bit |
|---|
| 864 | # lower down, in fact) |
|---|
| 865 | } |
|---|
| 866 | } |
|---|
| 867 | |
|---|
| 868 | if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) { |
|---|
| 869 | $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] ); |
|---|
| 870 | if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) { |
|---|
| 871 | $this->loadDefaults(); // Possible collision! |
|---|
| 872 | wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and |
|---|
| 873 | cookie user ID ($sId) don't match!" ); |
|---|
| 874 | return false; |
|---|
| 875 | } |
|---|
| 876 | $_SESSION['wsUserID'] = $sId; |
|---|
| 877 | } else if ( isset( $_SESSION['wsUserID'] ) ) { |
|---|
| 878 | if ( $_SESSION['wsUserID'] != 0 ) { |
|---|
| 879 | $sId = $_SESSION['wsUserID']; |
|---|
| 880 | } else { |
|---|
| 881 | $this->loadDefaults(); |
|---|
| 882 | return false; |
|---|
| 883 | } |
|---|
| 884 | } else { |
|---|
| 885 | $this->loadDefaults(); |
|---|
| 886 | return false; |
|---|
| 887 | } |
|---|
| 888 | |
|---|
| 889 | if ( isset( $_SESSION['wsUserName'] ) ) { |
|---|
| 890 | $sName = $_SESSION['wsUserName']; |
|---|
| 891 | } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) { |
|---|
| 892 | $sName = $_COOKIE["{$wgCookiePrefix}UserName"]; |
|---|
| 893 | $_SESSION['wsUserName'] = $sName; |
|---|
| 894 | } else { |
|---|
| 895 | $this->loadDefaults(); |
|---|
| 896 | return false; |
|---|
| 897 | } |
|---|
| 898 | |
|---|
| 899 | $passwordCorrect = FALSE; |
|---|
| 900 | $this->mId = $sId; |
|---|
| 901 | if ( !$this->loadFromId() ) { |
|---|
| 902 | # Not a valid ID, loadFromId has switched the object to anon for us |
|---|
| 903 | return false; |
|---|
| 904 | } |
|---|
| 905 | |
|---|
| 906 | global $wgBlockDisablesLogin; |
|---|
| 907 | if( $wgBlockDisablesLogin && $this->isBlocked() ) { |
|---|
| 908 | # User blocked and we've disabled blocked user logins |
|---|
| 909 | $this->loadDefaults(); |
|---|
| 910 | return false; |
|---|
| 911 | } |
|---|
| 912 | |
|---|
| 913 | if ( isset( $_SESSION['wsToken'] ) ) { |
|---|
| 914 | $passwordCorrect = $_SESSION['wsToken'] == $this->mToken; |
|---|
| 915 | $from = 'session'; |
|---|
| 916 | } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) { |
|---|
| 917 | $passwordCorrect = $this->mToken == $_COOKIE["{$wgCookiePrefix}Token"]; |
|---|
| 918 | $from = 'cookie'; |
|---|
| 919 | } else { |
|---|
| 920 | # No session or persistent login cookie |
|---|
| 921 | $this->loadDefaults(); |
|---|
| 922 | return false; |
|---|
| 923 | } |
|---|
| 924 | |
|---|
| 925 | if ( ( $sName == $this->mName ) && $passwordCorrect ) { |
|---|
| 926 | $_SESSION['wsToken'] = $this->mToken; |
|---|
| 927 | wfDebug( "Logged in from $from\n" ); |
|---|
| 928 | return true; |
|---|
| 929 | } else { |
|---|
| 930 | # Invalid credentials |
|---|
| 931 | wfDebug( "Can't log in from $from, invalid credentials\n" ); |
|---|
| 932 | $this->loadDefaults(); |
|---|
| 933 | return false; |
|---|
| 934 | } |
|---|
| 935 | } |
|---|
| 936 | |
|---|
| 937 | /** |
|---|
| 938 | * Load user and user_group data from the database. |
|---|
| 939 | * $this::mId must be set, this is how the user is identified. |
|---|
| 940 | * |
|---|
| 941 | * @return \bool True if the user exists, false if the user is anonymous |
|---|
| 942 | * @private |
|---|
| 943 | */ |
|---|
| 944 | function loadFromDatabase() { |
|---|
| 945 | # Paranoia |
|---|
| 946 | $this->mId = intval( $this->mId ); |
|---|
| 947 | |
|---|
| 948 | /** Anonymous user */ |
|---|
| 949 | if( !$this->mId ) { |
|---|
| 950 | $this->loadDefaults(); |
|---|
| 951 | return false; |
|---|
| 952 | } |
|---|
| 953 | |
|---|
| 954 | $dbr = wfGetDB( DB_MASTER ); |
|---|
| 955 | $s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ ); |
|---|
| 956 | |
|---|
| 957 | wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) ); |
|---|
| 958 | |
|---|
| 959 | if ( $s !== false ) { |
|---|
| 960 | # Initialise user table data |
|---|
| 961 | $this->loadFromRow( $s ); |
|---|
| 962 | $this->mGroups = null; // deferred |
|---|
| 963 | $this->getEditCount(); // revalidation for nulls |
|---|
| 964 | return true; |
|---|
| 965 | } else { |
|---|
| 966 | # Invalid user_id |
|---|
| 967 | $this->mId = 0; |
|---|
| 968 | $this->loadDefaults(); |
|---|
| 969 | return false; |
|---|
| 970 | } |
|---|
| 971 | } |
|---|
| 972 | |
|---|
| 973 | /** |
|---|
| 974 | * Initialize this object from a row from the user table. |
|---|
| 975 | * |
|---|
| 976 | * @param $row \type{\arrayof{\mixed}} Row from the user table to load. |
|---|
| 977 | */ |
|---|
| 978 | function loadFromRow( $row ) { |
|---|
| 979 | $this->mDataLoaded = true; |
|---|
| 980 | |
|---|
| 981 | if ( isset( $row->user_id ) ) { |
|---|
| 982 | $this->mId = intval( $row->user_id ); |
|---|
| 983 | } |
|---|
| 984 | $this->mName = $row->user_name; |
|---|
| 985 | $this->mRealName = $row->user_real_name; |
|---|
| 986 | $this->mPassword = $row->user_password; |
|---|
| 987 | $this->mNewpassword = $row->user_newpassword; |
|---|
| 988 | $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time ); |
|---|
| 989 | $this->mEmail = $row->user_email; |
|---|
| 990 | $this->decodeOptions( $row->user_options ); |
|---|
| 991 | $this->mTouched = wfTimestamp(TS_MW,$row->user_touched); |
|---|
| 992 | $this->mToken = $row->user_token; |
|---|
| 993 | $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated ); |
|---|
| 994 | $this->mEmailToken = $row->user_email_token; |
|---|
| 995 | $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires ); |
|---|
| 996 | $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration ); |
|---|
| 997 | $this->mEditCount = $row->user_editcount; |
|---|
| 998 | } |
|---|
| 999 | |
|---|
| 1000 | /** |
|---|
| 1001 | * Load the groups from the database if they aren't already loaded. |
|---|
| 1002 | * @private |
|---|
| 1003 | */ |
|---|
| 1004 | function loadGroups() { |
|---|
| 1005 | if ( is_null( $this->mGroups ) ) { |
|---|
| 1006 | $dbr = wfGetDB( DB_MASTER ); |
|---|
| 1007 | $res = $dbr->select( 'user_groups', |
|---|
| 1008 | array( 'ug_group' ), |
|---|
| 1009 | array( 'ug_user' => $this->mId ), |
|---|
| 1010 | __METHOD__ ); |
|---|
| 1011 | $this->mGroups = array(); |
|---|
| 1012 | while( $row = $dbr->fetchObject( $res ) ) { |
|---|
| 1013 | $this->mGroups[] = $row->ug_group; |
|---|
| 1014 | } |
|---|
| 1015 | } |
|---|
| 1016 | } |
|---|
| 1017 | |
|---|
| 1018 | /** |
|---|
| 1019 | * Clear various cached data stored in this object. |
|---|
| 1020 | * @param $reloadFrom \string Reload user and user_groups table data from a |
|---|
| 1021 | * given source. May be "name", "id", "defaults", "session", or false for |
|---|
| 1022 | * no reload. |
|---|
| 1023 | */ |
|---|
| 1024 | function clearInstanceCache( $reloadFrom = false ) { |
|---|
| 1025 | $this->mNewtalk = -1; |
|---|
| 1026 | $this->mDatePreference = null; |
|---|
| 1027 | $this->mBlockedby = -1; # Unset |
|---|
| 1028 | $this->mHash = false; |
|---|
| 1029 | $this->mSkin = null; |
|---|
| 1030 | $this->mRights = null; |
|---|
| 1031 | $this->mEffectiveGroups = null; |
|---|
| 1032 | $this->mOptions = null; |
|---|
| 1033 | |
|---|
| 1034 | if ( $reloadFrom ) { |
|---|
| 1035 | $this->mDataLoaded = false; |
|---|
| 1036 | $this->mFrom = $reloadFrom; |
|---|
| 1037 | } |
|---|
| 1038 | } |
|---|
| 1039 | |
|---|
| 1040 | /** |
|---|
| 1041 | * Combine the language default options with any site-specific options |
|---|
| 1042 | * and add the default language variants. |
|---|
| 1043 | * |
|---|
| 1044 | * @return \type{\arrayof{\string}} Array of options |
|---|
| 1045 | */ |
|---|
| 1046 | static function getDefaultOptions() { |
|---|
| 1047 | global $wgNamespacesToBeSearchedDefault; |
|---|
| 1048 | /** |
|---|
| 1049 | * Site defaults will override the global/language defaults |
|---|
| 1050 | */ |
|---|
| 1051 | global $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin; |
|---|
| 1052 | $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptionOverrides(); |
|---|
| 1053 | |
|---|
| 1054 | /** |
|---|
| 1055 | * default language setting |
|---|
| 1056 | */ |
|---|
| 1057 | $variant = $wgContLang->getPreferredVariant( false ); |
|---|
| 1058 | $defOpt['variant'] = $variant; |
|---|
| 1059 | $defOpt['language'] = $variant; |
|---|
| 1060 | foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) { |
|---|
| 1061 | $defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] ); |
|---|
| 1062 | } |
|---|
| 1063 | $defOpt['skin'] = $wgDefaultSkin; |
|---|
| 1064 | |
|---|
| 1065 | return $defOpt; |
|---|
| 1066 | } |
|---|
| 1067 | |
|---|
| 1068 | /** |
|---|
| 1069 | * Get a given default option value. |
|---|
| 1070 | * |
|---|
| 1071 | * @param $opt \string Name of option to retrieve |
|---|
| 1072 | * @return \string Default option value |
|---|
| 1073 | */ |
|---|
| 1074 | public static function getDefaultOption( $opt ) { |
|---|
| 1075 | $defOpts = self::getDefaultOptions(); |
|---|
| 1076 | if( isset( $defOpts[$opt] ) ) { |
|---|
| 1077 | return $defOpts[$opt]; |
|---|
| 1078 | } else { |
|---|
| 1079 | return null; |
|---|
| 1080 | } |
|---|
| 1081 | } |
|---|
| 1082 | |
|---|
| 1083 | /** |
|---|
| 1084 | * Get a list of user toggle names |
|---|
| 1085 | * @return \type{\arrayof{\string}} Array of user toggle names |
|---|
| 1086 | */ |
|---|
| 1087 | static function getToggles() { |
|---|
| 1088 | global $wgContLang, $wgUseRCPatrol; |
|---|
| 1089 | $extraToggles = array(); |
|---|
| 1090 | wfRunHooks( 'UserToggles', array( &$extraToggles ) ); |
|---|
| 1091 | if( $wgUseRCPatrol ) { |
|---|
| 1092 | $extraToggles[] = 'hidepatrolled'; |
|---|
| 1093 | $extraToggles[] = 'newpageshidepatrolled'; |
|---|
| 1094 | $extraToggles[] = 'watchlisthidepatrolled'; |
|---|
| 1095 | } |
|---|
| 1096 | return array_merge( self::$mToggles, $extraToggles, $wgContLang->getExtraUserToggles() ); |
|---|
| 1097 | } |
|---|
| 1098 | |
|---|
| 1099 | |
|---|
| 1100 | /** |
|---|
| 1101 | * Get blocking information |
|---|
| 1102 | * @private |
|---|
| 1103 | * @param $bFromSlave \bool Whether to check the slave database first. To |
|---|
| 1104 | * improve performance, non-critical checks are done |
|---|
| 1105 | * against slaves. Check when actually saving should be |
|---|
| 1106 | * done against master. |
|---|
| 1107 | */ |
|---|
| 1108 | function getBlockedStatus( $bFromSlave = true ) { |
|---|
| 1109 | global $wgProxyWhitelist, $wgUser; |
|---|
| 1110 | |
|---|
| 1111 | if ( -1 != $this->mBlockedby ) { |
|---|
| 1112 | wfDebug( "User::getBlockedStatus: already loaded.\n" ); |
|---|
| 1113 | return; |
|---|
| 1114 | } |
|---|
| 1115 | |
|---|
| 1116 | wfProfileIn( __METHOD__ ); |
|---|
| 1117 | wfDebug( __METHOD__.": checking...\n" ); |
|---|
| 1118 | |
|---|
| 1119 | // Initialize data... |
|---|
| 1120 | // Otherwise something ends up stomping on $this->mBlockedby when |
|---|
| 1121 | // things get lazy-loaded later, causing false positive block hits |
|---|
| 1122 | // due to -1 !== 0. Probably session-related... Nothing should be |
|---|
| 1123 | // overwriting mBlockedby, surely? |
|---|
| 1124 | $this->load(); |
|---|
| 1125 | |
|---|
| 1126 | $this->mBlockedby = 0; |
|---|
| 1127 | $this->mHideName = 0; |
|---|
| 1128 | $this->mAllowUsertalk = 0; |
|---|
| 1129 | |
|---|
| 1130 | # Check if we are looking at an IP or a logged-in user |
|---|
| 1131 | if ( $this->isIP( $this->getName() ) ) { |
|---|
| 1132 | $ip = $this->getName(); |
|---|
| 1133 | } else { |
|---|
| 1134 | # Check if we are looking at the current user |
|---|
| 1135 | # If we don't, and the user is logged in, we don't know about |
|---|
| 1136 | # his IP / autoblock status, so ignore autoblock of current user's IP |
|---|
| 1137 | if ( $this->getID() != $wgUser->getID() ) { |
|---|
| 1138 | $ip = ''; |
|---|
| 1139 | } else { |
|---|
| 1140 | # Get IP of current user |
|---|
| 1141 | $ip = wfGetIP(); |
|---|
| 1142 | } |
|---|
| 1143 | } |
|---|
| 1144 | |
|---|
| 1145 | if ( $this->isAllowed( 'ipblock-exempt' ) ) { |
|---|
| 1146 | # Exempt from all types of IP-block |
|---|
| 1147 | $ip = ''; |
|---|
| 1148 | } |
|---|
| 1149 | |
|---|
| 1150 | # User/IP blocking |
|---|
| 1151 | $this->mBlock = new Block(); |
|---|
| 1152 | $this->mBlock->fromMaster( !$bFromSlave ); |
|---|
| 1153 | if ( $this->mBlock->load( $ip , $this->mId ) ) { |
|---|
| 1154 | wfDebug( __METHOD__ . ": Found block.\n" ); |
|---|
| 1155 | $this->mBlockedby = $this->mBlock->mBy; |
|---|
| 1156 | if( $this->mBlockedby == "0" ) |
|---|
| 1157 | $this->mBlockedby = $this->mBlock->mByName; |
|---|
| 1158 | $this->mBlockreason = $this->mBlock->mReason; |
|---|
| 1159 | $this->mHideName = $this->mBlock->mHideName; |
|---|
| 1160 | $this->mAllowUsertalk = $this->mBlock->mAllowUsertalk; |
|---|
| 1161 | if ( $this->isLoggedIn() && $wgUser->getID() == $this->getID() ) { |
|---|
| 1162 | $this->spreadBlock(); |
|---|
| 1163 | } |
|---|
| 1164 | } else { |
|---|
| 1165 | // Bug 13611: don't remove mBlock here, to allow account creation blocks to |
|---|
| 1166 | // apply to users. Note that the existence of $this->mBlock is not used to |
|---|
| 1167 | // check for edit blocks, $this->mBlockedby is instead. |
|---|
| 1168 | } |
|---|
| 1169 | |
|---|
| 1170 | # Proxy blocking |
|---|
| 1171 | if ( !$this->isAllowed( 'proxyunbannable' ) && !in_array( $ip, $wgProxyWhitelist ) ) { |
|---|
| 1172 | # Local list |
|---|
| 1173 | if ( wfIsLocallyBlockedProxy( $ip ) ) { |
|---|
| 1174 | $this->mBlockedby = wfMsg( 'proxyblocker' ); |
|---|
| 1175 | $this->mBlockreason = wfMsg( 'proxyblockreason' ); |
|---|
| 1176 | } |
|---|
| 1177 | |
|---|
| 1178 | # DNSBL |
|---|
| 1179 | if ( !$this->mBlockedby && !$this->getID() ) { |
|---|
| 1180 | if ( $this->isDnsBlacklisted( $ip ) ) { |
|---|
| 1181 | $this->mBlockedby = wfMsg( 'sorbs' ); |
|---|
| 1182 | $this->mBlockreason = wfMsg( 'sorbsreason' ); |
|---|
| 1183 | } |
|---|
| 1184 | } |
|---|
| 1185 | } |
|---|
| 1186 | |
|---|
| 1187 | # Extensions |
|---|
| 1188 | wfRunHooks( 'GetBlockedStatus', array( &$this ) ); |
|---|
| 1189 | |
|---|
| 1190 | wfProfileOut( __METHOD__ ); |
|---|
| 1191 | } |
|---|
| 1192 | |
|---|
| 1193 | /** |
|---|
| 1194 | * Whether the given IP is in a DNS blacklist. |
|---|
| 1195 | * |
|---|
| 1196 | * @param $ip \string IP to check |
|---|
| 1197 | * @param $checkWhitelist Boolean: whether to check the whitelist first |
|---|
| 1198 | * @return \bool True if blacklisted. |
|---|
| 1199 | */ |
|---|
| 1200 | function isDnsBlacklisted( $ip, $checkWhitelist = false ) { |
|---|
| 1201 | global $wgEnableSorbs, $wgEnableDnsBlacklist, |
|---|
| 1202 | $wgSorbsUrl, $wgDnsBlacklistUrls, $wgProxyWhitelist; |
|---|
| 1203 | |
|---|
| 1204 | if ( !$wgEnableDnsBlacklist && !$wgEnableSorbs ) |
|---|
| 1205 | return false; |
|---|
| 1206 | |
|---|
| 1207 | if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) ) |
|---|
| 1208 | return false; |
|---|
| 1209 | |
|---|
| 1210 | $urls = array_merge( $wgDnsBlacklistUrls, (array)$wgSorbsUrl ); |
|---|
| 1211 | return $this->inDnsBlacklist( $ip, $urls ); |
|---|
| 1212 | } |
|---|
| 1213 | |
|---|
| 1214 | /** |
|---|
| 1215 | * Whether the given IP is in a given DNS blacklist. |
|---|
| 1216 | * |
|---|
| 1217 | * @param $ip \string IP to check |
|---|
| 1218 | * @param $bases \string or Array of Strings: URL of the DNS blacklist |
|---|
| 1219 | * @return \bool True if blacklisted. |
|---|
| 1220 | */ |
|---|
| 1221 | function inDnsBlacklist( $ip, $bases ) { |
|---|
| 1222 | wfProfileIn( __METHOD__ ); |
|---|
| 1223 | |
|---|
| 1224 | $found = false; |
|---|
| 1225 | $host = ''; |
|---|
| 1226 | // FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170) |
|---|
| 1227 | if( IP::isIPv4( $ip ) ) { |
|---|
| 1228 | # Reverse IP, bug 21255 |
|---|
| 1229 | $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) ); |
|---|
| 1230 | |
|---|
| 1231 | foreach( (array)$bases as $base ) { |
|---|
| 1232 | # Make hostname |
|---|
| 1233 | $host = "$ipReversed.$base"; |
|---|
| 1234 | |
|---|
| 1235 | # Send query |
|---|
| 1236 | $ipList = gethostbynamel( $host ); |
|---|
| 1237 | |
|---|
| 1238 | if( $ipList ) { |
|---|
| 1239 | wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" ); |
|---|
| 1240 | $found = true; |
|---|
| 1241 | break; |
|---|
| 1242 | } else { |
|---|
| 1243 | wfDebug( "Requested $host, not found in $base.\n" ); |
|---|
| 1244 | } |
|---|
| 1245 | } |
|---|
| 1246 | } |
|---|
| 1247 | |
|---|
| 1248 | wfProfileOut( __METHOD__ ); |
|---|
| 1249 | return $found; |
|---|
| 1250 | } |
|---|
| 1251 | |
|---|
| 1252 | /** |
|---|
| 1253 | * Is this user subject to rate limiting? |
|---|
| 1254 | * |
|---|
| 1255 | * @return \bool True if rate limited |
|---|
| 1256 | */ |
|---|
| 1257 | public function isPingLimitable() { |
|---|
| 1258 | global $wgRateLimitsExcludedGroups; |
|---|
| 1259 | global $wgRateLimitsExcludedIPs; |
|---|
| 1260 | if( array_intersect( $this->getEffectiveGroups(), $wgRateLimitsExcludedGroups ) ) { |
|---|
| 1261 | // Deprecated, but kept for backwards-compatibility config |
|---|
| 1262 | return false; |
|---|
| 1263 | } |
|---|
| 1264 | if( in_array( wfGetIP(), $wgRateLimitsExcludedIPs ) ) { |
|---|
| 1265 | // No other good way currently to disable rate limits |
|---|
| 1266 | // for specific IPs. :P |
|---|
| 1267 | // But this is a crappy hack and should die. |
|---|
| 1268 | return false; |
|---|
| 1269 | } |
|---|
| 1270 | return !$this->isAllowed('noratelimit'); |
|---|
| 1271 | } |
|---|
| 1272 | |
|---|
| 1273 | /** |
|---|
| 1274 | * Primitive rate limits: enforce maximum actions per time period |
|---|
| 1275 | * to put a brake on flooding. |
|---|
| 1276 | * |
|---|
| 1277 | * @note When using a shared cache like memcached, IP-address |
|---|
| 1278 | * last-hit counters will be shared across wikis. |
|---|
| 1279 | * |
|---|
| 1280 | * @param $action \string Action to enforce; 'edit' if unspecified |
|---|
| 1281 | * @return \bool True if a rate limiter was tripped |
|---|
| 1282 | */ |
|---|
| 1283 | function pingLimiter( $action = 'edit' ) { |
|---|
| 1284 | # Call the 'PingLimiter' hook |
|---|
| 1285 | $result = false; |
|---|
| 1286 | if( !wfRunHooks( 'PingLimiter', array( &$this, $action, $result ) ) ) { |
|---|
| 1287 | return $result; |
|---|
| 1288 | } |
|---|
| 1289 | |
|---|
| 1290 | global $wgRateLimits; |
|---|
| 1291 | if( !isset( $wgRateLimits[$action] ) ) { |
|---|
| 1292 | return false; |
|---|
| 1293 | } |
|---|
| 1294 | |
|---|
| 1295 | # Some groups shouldn't trigger the ping limiter, ever |
|---|
| 1296 | if( !$this->isPingLimitable() ) |
|---|
| 1297 | return false; |
|---|
| 1298 | |
|---|
| 1299 | global $wgMemc, $wgRateLimitLog; |
|---|
| 1300 | wfProfileIn( __METHOD__ ); |
|---|
| 1301 | |
|---|
| 1302 | $limits = $wgRateLimits[$action]; |
|---|
| 1303 | $keys = array(); |
|---|
| 1304 | $id = $this->getId(); |
|---|
| 1305 | $ip = wfGetIP(); |
|---|
| 1306 | $userLimit = false; |
|---|
| 1307 | |
|---|
| 1308 | if( isset( $limits['anon'] ) && $id == 0 ) { |
|---|
| 1309 | $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon']; |
|---|
| 1310 | } |
|---|
| 1311 | |
|---|
| 1312 | if( isset( $limits['user'] ) && $id != 0 ) { |
|---|
| 1313 | $userLimit = $limits['user']; |
|---|
| 1314 | } |
|---|
| 1315 | if( $this->isNewbie() ) { |
|---|
| 1316 | if( isset( $limits['newbie'] ) && $id != 0 ) { |
|---|
| 1317 | $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie']; |
|---|
| 1318 | } |
|---|
| 1319 | if( isset( $limits['ip'] ) ) { |
|---|
| 1320 | $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip']; |
|---|
| 1321 | } |
|---|
| 1322 | $matches = array(); |
|---|
| 1323 | if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) { |
|---|
| 1324 | $subnet = $matches[1]; |
|---|
| 1325 | $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet']; |
|---|
| 1326 | } |
|---|
| 1327 | } |
|---|
| 1328 | // Check for group-specific permissions |
|---|
| 1329 | // If more than one group applies, use the group with the highest limit |
|---|
| 1330 | foreach ( $this->getGroups() as $group ) { |
|---|
| 1331 | if ( isset( $limits[$group] ) ) { |
|---|
| 1332 | if ( $userLimit === false || $limits[$group] > $userLimit ) { |
|---|
| 1333 | $userLimit = $limits[$group]; |
|---|
| 1334 | } |
|---|
| 1335 | } |
|---|
| 1336 | } |
|---|
| 1337 | // Set the user limit key |
|---|
| 1338 | if ( $userLimit !== false ) { |
|---|
| 1339 | wfDebug( __METHOD__ . ": effective user limit: $userLimit\n" ); |
|---|
| 1340 | $keys[ wfMemcKey( 'limiter', $action, 'user', $id ) ] = $userLimit; |
|---|
| 1341 | } |
|---|
| 1342 | |
|---|
| 1343 | $triggered = false; |
|---|
| 1344 | foreach( $keys as $key => $limit ) { |
|---|
| 1345 | list( $max, $period ) = $limit; |
|---|
| 1346 | $summary = "(limit $max in {$period}s)"; |
|---|
| 1347 | $count = $wgMemc->get( $key ); |
|---|
| 1348 | // Already pinged? |
|---|
| 1349 | if( $count ) { |
|---|
| 1350 | if( $count > $max ) { |
|---|
| 1351 | wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" ); |
|---|
| 1352 | if( $wgRateLimitLog ) { |
|---|
| 1353 | @error_log( wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog ); |
|---|
| 1354 | } |
|---|
| 1355 | $triggered = true; |
|---|
| 1356 | } else { |
|---|
| 1357 | wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" ); |
|---|
| 1358 | } |
|---|
| 1359 | } else { |
|---|
| 1360 | wfDebug( __METHOD__ . ": adding record for $key $summary\n" ); |
|---|
| 1361 | $wgMemc->add( $key, 0, intval( $period ) ); // first ping |
|---|
| 1362 | } |
|---|
| 1363 | $wgMemc->incr( $key ); |
|---|
| 1364 | } |
|---|
| 1365 | |
|---|
| 1366 | wfProfileOut( __METHOD__ ); |
|---|
| 1367 | return $triggered; |
|---|
| 1368 | } |
|---|
| 1369 | |
|---|
| 1370 | /** |
|---|
| 1371 | * Check if user is blocked |
|---|
| 1372 | * |
|---|
| 1373 | * @param $bFromSlave \bool Whether to check the slave database instead of the master |
|---|
| 1374 | * @return \bool True if blocked, false otherwise |
|---|
| 1375 | */ |
|---|
| 1376 | function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site |
|---|
| 1377 | wfDebug( "User::isBlocked: enter\n" ); |
|---|
| 1378 | $this->getBlockedStatus( $bFromSlave ); |
|---|
| 1379 | return $this->mBlockedby !== 0; |
|---|
| 1380 | } |
|---|
| 1381 | |
|---|
| 1382 | /** |
|---|
| 1383 | * Check if user is blocked from editing a particular article |
|---|
| 1384 | * |
|---|
| 1385 | * @param $title \string Title to check |
|---|
| 1386 | * @param $bFromSlave \bool Whether to check the slave database instead of the master |
|---|
| 1387 | * @return \bool True if blocked, false otherwise |
|---|
| 1388 | */ |
|---|
| 1389 | function isBlockedFrom( $title, $bFromSlave = false ) { |
|---|
| 1390 | global $wgBlockAllowsUTEdit; |
|---|
| 1391 | wfProfileIn( __METHOD__ ); |
|---|
| 1392 | wfDebug( __METHOD__ . ": enter\n" ); |
|---|
| 1393 | |
|---|
| 1394 | wfDebug( __METHOD__ . ": asking isBlocked()\n" ); |
|---|
| 1395 | $blocked = $this->isBlocked( $bFromSlave ); |
|---|
| 1396 | $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false ); |
|---|
| 1397 | # If a user's name is suppressed, they cannot make edits anywhere |
|---|
| 1398 | if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() && |
|---|
| 1399 | $title->getNamespace() == NS_USER_TALK ) { |
|---|
| 1400 | $blocked = false; |
|---|
| 1401 | wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" ); |
|---|
| 1402 | } |
|---|
| 1403 | |
|---|
| 1404 | wfRunHooks( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) ); |
|---|
| 1405 | |
|---|
| 1406 | wfProfileOut( __METHOD__ ); |
|---|
| 1407 | return $blocked; |
|---|
| 1408 | } |
|---|
| 1409 | |
|---|
| 1410 | /** |
|---|
| 1411 | * If user is blocked, return the name of the user who placed the block |
|---|
| 1412 | * @return \string name of blocker |
|---|
| 1413 | */ |
|---|
| 1414 | function blockedBy() { |
|---|
| 1415 | $this->getBlockedStatus(); |
|---|
| 1416 | return $this->mBlockedby; |
|---|
| 1417 | } |
|---|
| 1418 | |
|---|
| 1419 | /** |
|---|
| 1420 | * If user is blocked, return the specified reason for the block |
|---|
| 1421 | * @return \string Blocking reason |
|---|
| 1422 | */ |
|---|
| 1423 | function blockedFor() { |
|---|
| 1424 | $this->getBlockedStatus(); |
|---|
| 1425 | return $this->mBlockreason; |
|---|
| 1426 | } |
|---|
| 1427 | |
|---|
| 1428 | /** |
|---|
| 1429 | * If user is blocked, return the ID for the block |
|---|
| 1430 | * @return \int Block ID |
|---|
| 1431 | */ |
|---|
| 1432 | function getBlockId() { |
|---|
| 1433 | $this->getBlockedStatus(); |
|---|
| 1434 | return ( $this->mBlock ? $this->mBlock->mId : false ); |
|---|
| 1435 | } |
|---|
| 1436 | |
|---|
| 1437 | /** |
|---|
| 1438 | * Check if user is blocked on all wikis. |
|---|
| 1439 | * Do not use for actual edit permission checks! |
|---|
| 1440 | * This is intented for quick UI checks. |
|---|
| 1441 | * |
|---|
| 1442 | * @param $ip \type{\string} IP address, uses current client if none given |
|---|
| 1443 | * @return \type{\bool} True if blocked, false otherwise |
|---|
| 1444 | */ |
|---|
| 1445 | function isBlockedGlobally( $ip = '' ) { |
|---|
| 1446 | if( $this->mBlockedGlobally !== null ) { |
|---|
| 1447 | return $this->mBlockedGlobally; |
|---|
| 1448 | } |
|---|
| 1449 | // User is already an IP? |
|---|
| 1450 | if( IP::isIPAddress( $this->getName() ) ) { |
|---|
| 1451 | $ip = $this->getName(); |
|---|
| 1452 | } else if( !$ip ) { |
|---|
| 1453 | $ip = wfGetIP(); |
|---|
| 1454 | } |
|---|
| 1455 | $blocked = false; |
|---|
| 1456 | wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) ); |
|---|
| 1457 | $this->mBlockedGlobally = (bool)$blocked; |
|---|
| 1458 | return $this->mBlockedGlobally; |
|---|
| 1459 | } |
|---|
| 1460 | |
|---|
| 1461 | /** |
|---|
| 1462 | * Check if user account is locked |
|---|
| 1463 | * |
|---|
| 1464 | * @return \type{\bool} True if locked, false otherwise |
|---|
| 1465 | */ |
|---|
| 1466 | function isLocked() { |
|---|
| 1467 | if( $this->mLocked !== null ) { |
|---|
| 1468 | return $this->mLocked; |
|---|
| 1469 | } |
|---|
| 1470 | global $wgAuth; |
|---|
| 1471 | $authUser = $wgAuth->getUserInstance( $this ); |
|---|
| 1472 | $this->mLocked = (bool)$authUser->isLocked(); |
|---|
| 1473 | return $this->mLocked; |
|---|
| 1474 | } |
|---|
| 1475 | |
|---|
| 1476 | /** |
|---|
| 1477 | * Check if user account is hidden |
|---|
| 1478 | * |
|---|
| 1479 | * @return \type{\bool} True if hidden, false otherwise |
|---|
| 1480 | */ |
|---|
| 1481 | function isHidden() { |
|---|
| 1482 | if( $this->mHideName !== null ) { |
|---|
| 1483 | return $this->mHideName; |
|---|
| 1484 | } |
|---|
| 1485 | $this->getBlockedStatus(); |
|---|
| 1486 | if( !$this->mHideName ) { |
|---|
| 1487 | global $wgAuth; |
|---|
| 1488 | $authUser = $wgAuth->getUserInstance( $this ); |
|---|
| 1489 | $this->mHideName = (bool)$authUser->isHidden(); |
|---|
| 1490 | } |
|---|
| 1491 | return $this->mHideName; |
|---|
| 1492 | } |
|---|
| 1493 | |
|---|
| 1494 | /** |
|---|
| 1495 | * Get the user's ID. |
|---|
| 1496 | * @return \int The user's ID; 0 if the user is anonymous or nonexistent |
|---|
| 1497 | */ |
|---|
| 1498 | function getId() { |
|---|
| 1499 | if( $this->mId === null and $this->mName !== null |
|---|
| 1500 | and User::isIP( $this->mName ) ) { |
|---|
| 1501 | // Special case, we know the user is anonymous |
|---|
| 1502 | return 0; |
|---|
| 1503 | } elseif( $this->mId === null ) { |
|---|
| 1504 | // Don't load if this was initialized from an ID |
|---|
| 1505 | $this->load(); |
|---|
| 1506 | } |
|---|
| 1507 | return $this->mId; |
|---|
| 1508 | } |
|---|
| 1509 | |
|---|
| 1510 | /** |
|---|
| 1511 | * Set the user and reload all fields according to a given ID |
|---|
| 1512 | * @param $v \int User ID to reload |
|---|
| 1513 | */ |
|---|
| 1514 | function setId( $v ) { |
|---|
| 1515 | $this->mId = $v; |
|---|
| 1516 | $this->clearInstanceCache( 'id' ); |
|---|
| 1517 | } |
|---|
| 1518 | |
|---|
| 1519 | /** |
|---|
| 1520 | * Get the user name, or the IP of an anonymous user |
|---|
| 1521 | * @return \string User's name or IP address |
|---|
| 1522 | */ |
|---|
| 1523 | function getName() { |
|---|
| 1524 | if ( !$this->mDataLoaded && $this->mFrom == 'name' ) { |
|---|
| 1525 | # Special case optimisation |
|---|
| 1526 | return $this->mName; |
|---|
| 1527 | } else { |
|---|
| 1528 | $this->load(); |
|---|
| 1529 | if ( $this->mName === false ) { |
|---|
| 1530 | # Clean up IPs |
|---|
| 1531 | $this->mName = IP::sanitizeIP( wfGetIP() ); |
|---|
| 1532 | } |
|---|
| 1533 | return $this->mName; |
|---|
| 1534 | } |
|---|
| 1535 | } |
|---|
| 1536 | |
|---|
| 1537 | /** |
|---|
| 1538 | * Set the user name. |
|---|
| 1539 | * |
|---|
| 1540 | * This does not reload fields from the database according to the given |
|---|
| 1541 | * name. Rather, it is used to create a temporary "nonexistent user" for |
|---|
| 1542 | * later addition to the database. It can also be used to set the IP |
|---|
| 1543 | * address for an anonymous user to something other than the current |
|---|
| 1544 | * remote IP. |
|---|
| 1545 | * |
|---|
| 1546 | * @note User::newFromName() has rougly the same function, when the named user |
|---|
| 1547 | * does not exist. |
|---|
| 1548 | * @param $str \string New user name to set |
|---|
| 1549 | */ |
|---|
| 1550 | function setName( $str ) { |
|---|
| 1551 | $this->load(); |
|---|
| 1552 | $this->mName = $str; |
|---|
| 1553 | } |
|---|
| 1554 | |
|---|
| 1555 | /** |
|---|
| 1556 | * Get the user's name escaped by underscores. |
|---|
| 1557 | * @return \string Username escaped by underscores. |
|---|
| 1558 | */ |
|---|
| 1559 | function getTitleKey() { |
|---|
| 1560 | return str_replace( ' ', '_', $this->getName() ); |
|---|
| 1561 | } |
|---|
| 1562 | |
|---|
| 1563 | /** |
|---|
| 1564 | * Check if the user has new messages. |
|---|
| 1565 | * @return \bool True if the user has new messages |
|---|
| 1566 | */ |
|---|
| 1567 | function getNewtalk() { |
|---|
| 1568 | $this->load(); |
|---|
| 1569 | |
|---|
| 1570 | # Load the newtalk status if it is unloaded (mNewtalk=-1) |
|---|
| 1571 | if( $this->mNewtalk === -1 ) { |
|---|
| 1572 | $this->mNewtalk = false; # reset talk page status |
|---|
| 1573 | |
|---|
| 1574 | # Check memcached separately for anons, who have no |
|---|
| 1575 | # entire User object stored in there. |
|---|
| 1576 | if( !$this->mId ) { |
|---|
| 1577 | global $wgMemc; |
|---|
| 1578 | $key = wfMemcKey( 'newtalk', 'ip', $this->getName() ); |
|---|
| 1579 | $newtalk = $wgMemc->get( $key ); |
|---|
| 1580 | if( strval( $newtalk ) !== '' ) { |
|---|
| 1581 | $this->mNewtalk = (bool)$newtalk; |
|---|
| 1582 | } else { |
|---|
| 1583 | // Since we are caching this, make sure it is up to date by getting it |
|---|
| 1584 | // from the master |
|---|
| 1585 | $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true ); |
|---|
| 1586 | $wgMemc->set( $key, (int)$this->mNewtalk, 1800 ); |
|---|
| 1587 | } |
|---|
| 1588 | } else { |
|---|
| 1589 | $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId ); |
|---|
| 1590 | } |
|---|
| 1591 | } |
|---|
| 1592 | |
|---|
| 1593 | return (bool)$this->mNewtalk; |
|---|
| 1594 | } |
|---|
| 1595 | |
|---|
| 1596 | /** |
|---|
| 1597 | * Return the talk page(s) this user has new messages on. |
|---|
| 1598 | * @return \type{\arrayof{\string}} Array of page URLs |
|---|
| 1599 | */ |
|---|
| 1600 | function getNewMessageLinks() { |
|---|
| 1601 | $talks = array(); |
|---|
| 1602 | if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) |
|---|
| 1603 | return $talks; |
|---|
| 1604 | |
|---|
| 1605 | if( !$this->getNewtalk() ) |
|---|
| 1606 | return array(); |
|---|
| 1607 | $up = $this->getUserPage(); |
|---|
| 1608 | $utp = $up->getTalkPage(); |
|---|
| 1609 | return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL() ) ); |
|---|
| 1610 | } |
|---|
| 1611 | |
|---|
| 1612 | /** |
|---|
| 1613 | * Internal uncached check for new messages |
|---|
| 1614 | * |
|---|
| 1615 | * @see getNewtalk() |
|---|
| 1616 | * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise |
|---|
| 1617 | * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise |
|---|
| 1618 | * @param $fromMaster \bool true to fetch from the master, false for a slave |
|---|
| 1619 | * @return \bool True if the user has new messages |
|---|
| 1620 | * @private |
|---|
| 1621 | */ |
|---|
| 1622 | function checkNewtalk( $field, $id, $fromMaster = false ) { |
|---|
| 1623 | if ( $fromMaster ) { |
|---|
| 1624 | $db = wfGetDB( DB_MASTER ); |
|---|
| 1625 | } else { |
|---|
| 1626 | $db = wfGetDB( DB_SLAVE ); |
|---|
| 1627 | } |
|---|
| 1628 | $ok = $db->selectField( 'user_newtalk', $field, |
|---|
| 1629 | array( $field => $id ), __METHOD__ ); |
|---|
| 1630 | return $ok !== false; |
|---|
| 1631 | } |
|---|
| 1632 | |
|---|
| 1633 | /** |
|---|
| 1634 | * Add or update the new messages flag |
|---|
| 1635 | * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise |
|---|
| 1636 | * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise |
|---|
| 1637 | * @return \bool True if successful, false otherwise |
|---|
| 1638 | * @private |
|---|
| 1639 | */ |
|---|
| 1640 | function updateNewtalk( $field, $id ) { |
|---|
| 1641 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 1642 | $dbw->insert( 'user_newtalk', |
|---|
| 1643 | array( $field => $id ), |
|---|
| 1644 | __METHOD__, |
|---|
| 1645 | 'IGNORE' ); |
|---|
| 1646 | if ( $dbw->affectedRows() ) { |
|---|
| 1647 | wfDebug( __METHOD__ . ": set on ($field, $id)\n" ); |
|---|
| 1648 | return true; |
|---|
| 1649 | } else { |
|---|
| 1650 | wfDebug( __METHOD__ . " already set ($field, $id)\n" ); |
|---|
| 1651 | return false; |
|---|
| 1652 | } |
|---|
| 1653 | } |
|---|
| 1654 | |
|---|
| 1655 | /** |
|---|
| 1656 | * Clear the new messages flag for the given user |
|---|
| 1657 | * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise |
|---|
| 1658 | * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise |
|---|
| 1659 | * @return \bool True if successful, false otherwise |
|---|
| 1660 | * @private |
|---|
| 1661 | */ |
|---|
| 1662 | function deleteNewtalk( $field, $id ) { |
|---|
| 1663 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 1664 | $dbw->delete( 'user_newtalk', |
|---|
| 1665 | array( $field => $id ), |
|---|
| 1666 | __METHOD__ ); |
|---|
| 1667 | if ( $dbw->affectedRows() ) { |
|---|
| 1668 | wfDebug( __METHOD__ . ": killed on ($field, $id)\n" ); |
|---|
| 1669 | return true; |
|---|
| 1670 | } else { |
|---|
| 1671 | wfDebug( __METHOD__ . ": already gone ($field, $id)\n" ); |
|---|
| 1672 | return false; |
|---|
| 1673 | } |
|---|
| 1674 | } |
|---|
| 1675 | |
|---|
| 1676 | /** |
|---|
| 1677 | * Update the 'You have new messages!' status. |
|---|
| 1678 | * @param $val \bool Whether the user has new messages |
|---|
| 1679 | */ |
|---|
| 1680 | function setNewtalk( $val ) { |
|---|
| 1681 | if( wfReadOnly() ) { |
|---|
| 1682 | return; |
|---|
| 1683 | } |
|---|
| 1684 | |
|---|
| 1685 | $this->load(); |
|---|
| 1686 | $this->mNewtalk = $val; |
|---|
| 1687 | |
|---|
| 1688 | if( $this->isAnon() ) { |
|---|
| 1689 | $field = 'user_ip'; |
|---|
| 1690 | $id = $this->getName(); |
|---|
| 1691 | } else { |
|---|
| 1692 | $field = 'user_id'; |
|---|
| 1693 | $id = $this->getId(); |
|---|
| 1694 | } |
|---|
| 1695 | global $wgMemc; |
|---|
| 1696 | |
|---|
| 1697 | if( $val ) { |
|---|
| 1698 | $changed = $this->updateNewtalk( $field, $id ); |
|---|
| 1699 | } else { |
|---|
| 1700 | $changed = $this->deleteNewtalk( $field, $id ); |
|---|
| 1701 | } |
|---|
| 1702 | |
|---|
| 1703 | if( $this->isAnon() ) { |
|---|
| 1704 | // Anons have a separate memcached space, since |
|---|
| 1705 | // user records aren't kept for them. |
|---|
| 1706 | $key = wfMemcKey( 'newtalk', 'ip', $id ); |
|---|
| 1707 | $wgMemc->set( $key, $val ? 1 : 0, 1800 ); |
|---|
| 1708 | } |
|---|
| 1709 | if ( $changed ) { |
|---|
| 1710 | $this->invalidateCache(); |
|---|
| 1711 | } |
|---|
| 1712 | } |
|---|
| 1713 | |
|---|
| 1714 | /** |
|---|
| 1715 | * Generate a current or new-future timestamp to be stored in the |
|---|
| 1716 | * user_touched field when we update things. |
|---|
| 1717 | * @return \string Timestamp in TS_MW format |
|---|
| 1718 | */ |
|---|
| 1719 | private static function newTouchedTimestamp() { |
|---|
| 1720 | global $wgClockSkewFudge; |
|---|
| 1721 | return wfTimestamp( TS_MW, time() + $wgClockSkewFudge ); |
|---|
| 1722 | } |
|---|
| 1723 | |
|---|
| 1724 | /** |
|---|
| 1725 | * Clear user data from memcached. |
|---|
| 1726 | * Use after applying fun updates to the database; caller's |
|---|
| 1727 | * responsibility to update user_touched if appropriate. |
|---|
| 1728 | * |
|---|
| 1729 | * Called implicitly from invalidateCache() and saveSettings(). |
|---|
| 1730 | */ |
|---|
| 1731 | private function clearSharedCache() { |
|---|
| 1732 | $this->load(); |
|---|
| 1733 | if( $this->mId ) { |
|---|
| 1734 | global $wgMemc; |
|---|
| 1735 | $wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) ); |
|---|
| 1736 | } |
|---|
| 1737 | } |
|---|
| 1738 | |
|---|
| 1739 | /** |
|---|
| 1740 | * Immediately touch the user data cache for this account. |
|---|
| 1741 | * Updates user_touched field, and removes account data from memcached |
|---|
| 1742 | * for reload on the next hit. |
|---|
| 1743 | */ |
|---|
| 1744 | function invalidateCache() { |
|---|
| 1745 | if( wfReadOnly() ) { |
|---|
| 1746 | return; |
|---|
| 1747 | } |
|---|
| 1748 | $this->load(); |
|---|
| 1749 | if( $this->mId ) { |
|---|
| 1750 | $this->mTouched = self::newTouchedTimestamp(); |
|---|
| 1751 | |
|---|
| 1752 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 1753 | $dbw->update( 'user', |
|---|
| 1754 | array( 'user_touched' => $dbw->timestamp( $this->mTouched ) ), |
|---|
| 1755 | array( 'user_id' => $this->mId ), |
|---|
| 1756 | __METHOD__ ); |
|---|
| 1757 | |
|---|
| 1758 | $this->clearSharedCache(); |
|---|
| 1759 | } |
|---|
| 1760 | } |
|---|
| 1761 | |
|---|
| 1762 | /** |
|---|
| 1763 | * Validate the cache for this account. |
|---|
| 1764 | * @param $timestamp \string A timestamp in TS_MW format |
|---|
| 1765 | */ |
|---|
| 1766 | function validateCache( $timestamp ) { |
|---|
| 1767 | $this->load(); |
|---|
| 1768 | return ( $timestamp >= $this->mTouched ); |
|---|
| 1769 | } |
|---|
| 1770 | |
|---|
| 1771 | /** |
|---|
| 1772 | * Get the user touched timestamp |
|---|
| 1773 | */ |
|---|
| 1774 | function getTouched() { |
|---|
| 1775 | $this->load(); |
|---|
| 1776 | return $this->mTouched; |
|---|
| 1777 | } |
|---|
| 1778 | |
|---|
| 1779 | /** |
|---|
| 1780 | * Set the password and reset the random token. |
|---|
| 1781 | * Calls through to authentication plugin if necessary; |
|---|
| 1782 | * will have no effect if the auth plugin refuses to |
|---|
| 1783 | * pass the change through or if the legal password |
|---|
| 1784 | * checks fail. |
|---|
| 1785 | * |
|---|
| 1786 | * As a special case, setting the password to null |
|---|
| 1787 | * wipes it, so the account cannot be logged in until |
|---|
| 1788 | * a new password is set, for instance via e-mail. |
|---|
| 1789 | * |
|---|
| 1790 | * @param $str \string New password to set |
|---|
| 1791 | * @throws PasswordError on failure |
|---|
| 1792 | */ |
|---|
| 1793 | function setPassword( $str ) { |
|---|
| 1794 | global $wgAuth; |
|---|
| 1795 | |
|---|
| 1796 | if( $str !== null ) { |
|---|
| 1797 | if( !$wgAuth->allowPasswordChange() ) { |
|---|
| 1798 | throw new PasswordError( wfMsg( 'password-change-forbidden' ) ); |
|---|
| 1799 | } |
|---|
| 1800 | |
|---|
| 1801 | if( !$this->isValidPassword( $str ) ) { |
|---|
| 1802 | global $wgMinimalPasswordLength; |
|---|
| 1803 | $valid = $this->getPasswordValidity( $str ); |
|---|
| 1804 | throw new PasswordError( wfMsgExt( $valid, array( 'parsemag' ), |
|---|
| 1805 | $wgMinimalPasswordLength ) ); |
|---|
| 1806 | } |
|---|
| 1807 | } |
|---|
| 1808 | |
|---|
| 1809 | if( !$wgAuth->setPassword( $this, $str ) ) { |
|---|
| 1810 | throw new PasswordError( wfMsg( 'externaldberror' ) ); |
|---|
| 1811 | } |
|---|
| 1812 | |
|---|
| 1813 | $this->setInternalPassword( $str ); |
|---|
| 1814 | |
|---|
| 1815 | return true; |
|---|
| 1816 | } |
|---|
| 1817 | |
|---|
| 1818 | /** |
|---|
| 1819 | * Set the password and reset the random token unconditionally. |
|---|
| 1820 | * |
|---|
| 1821 | * @param $str \string New password to set |
|---|
| 1822 | */ |
|---|
| 1823 | function setInternalPassword( $str ) { |
|---|
| 1824 | $this->load(); |
|---|
| 1825 | $this->setToken(); |
|---|
| 1826 | |
|---|
| 1827 | if( $str === null ) { |
|---|
| 1828 | // Save an invalid hash... |
|---|
| 1829 | $this->mPassword = ''; |
|---|
| 1830 | } else { |
|---|
| 1831 | $this->mPassword = self::crypt( $str ); |
|---|
| 1832 | } |
|---|
| 1833 | $this->mNewpassword = ''; |
|---|
| 1834 | $this->mNewpassTime = null; |
|---|
| 1835 | } |
|---|
| 1836 | |
|---|
| 1837 | /** |
|---|
| 1838 | * Get the user's current token. |
|---|
| 1839 | * @return \string Token |
|---|
| 1840 | */ |
|---|
| 1841 | function getToken() { |
|---|
| 1842 | $this->load(); |
|---|
| 1843 | return $this->mToken; |
|---|
| 1844 | } |
|---|
| 1845 | |
|---|
| 1846 | /** |
|---|
| 1847 | * Set the random token (used for persistent authentication) |
|---|
| 1848 | * Called from loadDefaults() among other places. |
|---|
| 1849 | * |
|---|
| 1850 | * @param $token \string If specified, set the token to this value |
|---|
| 1851 | * @private |
|---|
| 1852 | */ |
|---|
| 1853 | function setToken( $token = false ) { |
|---|
| 1854 | global $wgSecretKey, $wgProxyKey; |
|---|
| 1855 | $this->load(); |
|---|
| 1856 | if ( !$token ) { |
|---|
| 1857 | if ( $wgSecretKey ) { |
|---|
| 1858 | $key = $wgSecretKey; |
|---|
| 1859 | } elseif ( $wgProxyKey ) { |
|---|
| 1860 | $key = $wgProxyKey; |
|---|
| 1861 | } else { |
|---|
| 1862 | $key = microtime(); |
|---|
| 1863 | } |
|---|
| 1864 | $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . wfWikiID() . $this->mId ); |
|---|
| 1865 | } else { |
|---|
| 1866 | $this->mToken = $token; |
|---|
| 1867 | } |
|---|
| 1868 | } |
|---|
| 1869 | |
|---|
| 1870 | /** |
|---|
| 1871 | * Set the cookie password |
|---|
| 1872 | * |
|---|
| 1873 | * @param $str \string New cookie password |
|---|
| 1874 | * @private |
|---|
| 1875 | */ |
|---|
| 1876 | function setCookiePassword( $str ) { |
|---|
| 1877 | $this->load(); |
|---|
| 1878 | $this->mCookiePassword = md5( $str ); |
|---|
| 1879 | } |
|---|
| 1880 | |
|---|
| 1881 | /** |
|---|
| 1882 | * Set the password for a password reminder or new account email |
|---|
| 1883 | * |
|---|
| 1884 | * @param $str \string New password to set |
|---|
| 1885 | * @param $throttle \bool If true, reset the throttle timestamp to the present |
|---|
| 1886 | */ |
|---|
| 1887 | function setNewpassword( $str, $throttle = true ) { |
|---|
| 1888 | $this->load(); |
|---|
| 1889 | $this->mNewpassword = self::crypt( $str ); |
|---|
| 1890 | if ( $throttle ) { |
|---|
| 1891 | $this->mNewpassTime = wfTimestampNow(); |
|---|
| 1892 | } |
|---|
| 1893 | } |
|---|
| 1894 | |
|---|
| 1895 | /** |
|---|
| 1896 | * Has password reminder email been sent within the last |
|---|
| 1897 | * $wgPasswordReminderResendTime hours? |
|---|
| 1898 | * @return \bool True or false |
|---|
| 1899 | */ |
|---|
| 1900 | function isPasswordReminderThrottled() { |
|---|
| 1901 | global $wgPasswordReminderResendTime; |
|---|
| 1902 | $this->load(); |
|---|
| 1903 | if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) { |
|---|
| 1904 | return false; |
|---|
| 1905 | } |
|---|
| 1906 | $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgPasswordReminderResendTime * 3600; |
|---|
| 1907 | return time() < $expiry; |
|---|
| 1908 | } |
|---|
| 1909 | |
|---|
| 1910 | /** |
|---|
| 1911 | * Get the user's e-mail address |
|---|
| 1912 | * @return \string User's email address |
|---|
| 1913 | */ |
|---|
| 1914 | function getEmail() { |
|---|
| 1915 | $this->load(); |
|---|
| 1916 | wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) ); |
|---|
| 1917 | return $this->mEmail; |
|---|
| 1918 | } |
|---|
| 1919 | |
|---|
| 1920 | /** |
|---|
| 1921 | * Get the timestamp of the user's e-mail authentication |
|---|
| 1922 | * @return \string TS_MW timestamp |
|---|
| 1923 | */ |
|---|
| 1924 | function getEmailAuthenticationTimestamp() { |
|---|
| 1925 | $this->load(); |
|---|
| 1926 | wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) ); |
|---|
| 1927 | return $this->mEmailAuthenticated; |
|---|
| 1928 | } |
|---|
| 1929 | |
|---|
| 1930 | /** |
|---|
| 1931 | * Set the user's e-mail address |
|---|
| 1932 | * @param $str \string New e-mail address |
|---|
| 1933 | */ |
|---|
| 1934 | function setEmail( $str ) { |
|---|
| 1935 | $this->load(); |
|---|
| 1936 | $this->mEmail = $str; |
|---|
| 1937 | wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) ); |
|---|
| 1938 | } |
|---|
| 1939 | |
|---|
| 1940 | /** |
|---|
| 1941 | * Get the user's real name |
|---|
| 1942 | * @return \string User's real name |
|---|
| 1943 | */ |
|---|
| 1944 | function getRealName() { |
|---|
| 1945 | $this->load(); |
|---|
| 1946 | return $this->mRealName; |
|---|
| 1947 | } |
|---|
| 1948 | |
|---|
| 1949 | /** |
|---|
| 1950 | * Set the user's real name |
|---|
| 1951 | * @param $str \string New real name |
|---|
| 1952 | */ |
|---|
| 1953 | function setRealName( $str ) { |
|---|
| 1954 | $this->load(); |
|---|
| 1955 | $this->mRealName = $str; |
|---|
| 1956 | } |
|---|
| 1957 | |
|---|
| 1958 | /** |
|---|
| 1959 | * Get the user's current setting for a given option. |
|---|
| 1960 | * |
|---|
| 1961 | * @param $oname \string The option to check |
|---|
| 1962 | * @param $defaultOverride \string A default value returned if the option does not exist |
|---|
| 1963 | * @return \string User's current value for the option |
|---|
| 1964 | * @see getBoolOption() |
|---|
| 1965 | * @see getIntOption() |
|---|
| 1966 | */ |
|---|
| 1967 | function getOption( $oname, $defaultOverride = null ) { |
|---|
| 1968 | $this->loadOptions(); |
|---|
| 1969 | |
|---|
| 1970 | if ( is_null( $this->mOptions ) ) { |
|---|
| 1971 | if($defaultOverride != '') { |
|---|
| 1972 | return $defaultOverride; |
|---|
| 1973 | } |
|---|
| 1974 | $this->mOptions = User::getDefaultOptions(); |
|---|
| 1975 | } |
|---|
| 1976 | |
|---|
| 1977 | if ( array_key_exists( $oname, $this->mOptions ) ) { |
|---|
| 1978 | return $this->mOptions[$oname]; |
|---|
| 1979 | } else { |
|---|
| 1980 | return $defaultOverride; |
|---|
| 1981 | } |
|---|
| 1982 | } |
|---|
| 1983 | |
|---|
| 1984 | /** |
|---|
| 1985 | * Get all user's options |
|---|
| 1986 | * |
|---|
| 1987 | * @return array |
|---|
| 1988 | */ |
|---|
| 1989 | public function getOptions() { |
|---|
| 1990 | $this->loadOptions(); |
|---|
| 1991 | return $this->mOptions; |
|---|
| 1992 | } |
|---|
| 1993 | |
|---|
| 1994 | /** |
|---|
| 1995 | * Get the user's current setting for a given option, as a boolean value. |
|---|
| 1996 | * |
|---|
| 1997 | * @param $oname \string The option to check |
|---|
| 1998 | * @return \bool User's current value for the option |
|---|
| 1999 | * @see getOption() |
|---|
| 2000 | */ |
|---|
| 2001 | function getBoolOption( $oname ) { |
|---|
| 2002 | return (bool)$this->getOption( $oname ); |
|---|
| 2003 | } |
|---|
| 2004 | |
|---|
| 2005 | |
|---|
| 2006 | /** |
|---|
| 2007 | * Get the user's current setting for a given option, as a boolean value. |
|---|
| 2008 | * |
|---|
| 2009 | * @param $oname \string The option to check |
|---|
| 2010 | * @param $defaultOverride \int A default value returned if the option does not exist |
|---|
| 2011 | * @return \int User's current value for the option |
|---|
| 2012 | * @see getOption() |
|---|
| 2013 | */ |
|---|
| 2014 | function getIntOption( $oname, $defaultOverride=0 ) { |
|---|
| 2015 | $val = $this->getOption( $oname ); |
|---|
| 2016 | if( $val == '' ) { |
|---|
| 2017 | $val = $defaultOverride; |
|---|
| 2018 | } |
|---|
| 2019 | return intval( $val ); |
|---|
| 2020 | } |
|---|
| 2021 | |
|---|
| 2022 | /** |
|---|
| 2023 | * Set the given option for a user. |
|---|
| 2024 | * |
|---|
| 2025 | * @param $oname \string The option to set |
|---|
| 2026 | * @param $val \mixed New value to set |
|---|
| 2027 | */ |
|---|
| 2028 | function setOption( $oname, $val ) { |
|---|
| 2029 | $this->load(); |
|---|
| 2030 | $this->loadOptions(); |
|---|
| 2031 | |
|---|
| 2032 | if ( $oname == 'skin' ) { |
|---|
| 2033 | # Clear cached skin, so the new one displays immediately in Special:Preferences |
|---|
| 2034 | unset( $this->mSkin ); |
|---|
| 2035 | } |
|---|
| 2036 | |
|---|
| 2037 | // Explicitly NULL values should refer to defaults |
|---|
| 2038 | global $wgDefaultUserOptions; |
|---|
| 2039 | if( is_null( $val ) && isset( $wgDefaultUserOptions[$oname] ) ) { |
|---|
| 2040 | $val = $wgDefaultUserOptions[$oname]; |
|---|
| 2041 | } |
|---|
| 2042 | |
|---|
| 2043 | $this->mOptions[$oname] = $val; |
|---|
| 2044 | } |
|---|
| 2045 | |
|---|
| 2046 | /** |
|---|
| 2047 | * Reset all options to the site defaults |
|---|
| 2048 | */ |
|---|
| 2049 | function resetOptions() { |
|---|
| 2050 | $this->mOptions = User::getDefaultOptions(); |
|---|
| 2051 | } |
|---|
| 2052 | |
|---|
| 2053 | /** |
|---|
| 2054 | * Get the user's preferred date format. |
|---|
| 2055 | * @return \string User's preferred date format |
|---|
| 2056 | */ |
|---|
| 2057 | function getDatePreference() { |
|---|
| 2058 | // Important migration for old data rows |
|---|
| 2059 | if ( is_null( $this->mDatePreference ) ) { |
|---|
| 2060 | global $wgLang; |
|---|
| 2061 | $value = $this->getOption( 'date' ); |
|---|
| 2062 | $map = $wgLang->getDatePreferenceMigrationMap(); |
|---|
| 2063 | if ( isset( $map[$value] ) ) { |
|---|
| 2064 | $value = $map[$value]; |
|---|
| 2065 | } |
|---|
| 2066 | $this->mDatePreference = $value; |
|---|
| 2067 | } |
|---|
| 2068 | return $this->mDatePreference; |
|---|
| 2069 | } |
|---|
| 2070 | |
|---|
| 2071 | /** |
|---|
| 2072 | * Get the permissions this user has. |
|---|
| 2073 | * @return \type{\arrayof{\string}} Array of permission names |
|---|
| 2074 | */ |
|---|
| 2075 | function getRights() { |
|---|
| 2076 | if ( is_null( $this->mRights ) ) { |
|---|
| 2077 | $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() ); |
|---|
| 2078 | wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) ); |
|---|
| 2079 | // Force reindexation of rights when a hook has unset one of them |
|---|
| 2080 | $this->mRights = array_values( $this->mRights ); |
|---|
| 2081 | } |
|---|
| 2082 | return $this->mRights; |
|---|
| 2083 | } |
|---|
| 2084 | |
|---|
| 2085 | /** |
|---|
| 2086 | * Get the list of explicit group memberships this user has. |
|---|
| 2087 | * The implicit * and user groups are not included. |
|---|
| 2088 | * @return \type{\arrayof{\string}} Array of internal group names |
|---|
| 2089 | */ |
|---|
| 2090 | function getGroups() { |
|---|
| 2091 | $this->load(); |
|---|
| 2092 | return $this->mGroups; |
|---|
| 2093 | } |
|---|
| 2094 | |
|---|
| 2095 | /** |
|---|
| 2096 | * Get the list of implicit group memberships this user has. |
|---|
| 2097 | * This includes all explicit groups, plus 'user' if logged in, |
|---|
| 2098 | * '*' for all accounts and autopromoted groups |
|---|
| 2099 | * @param $recache \bool Whether to avoid the cache |
|---|
| 2100 | * @return \type{\arrayof{\string}} Array of internal group names |
|---|
| 2101 | */ |
|---|
| 2102 | function getEffectiveGroups( $recache = false ) { |
|---|
| 2103 | if ( $recache || is_null( $this->mEffectiveGroups ) ) { |
|---|
| 2104 | wfProfileIn( __METHOD__ ); |
|---|
| 2105 | $this->mEffectiveGroups = $this->getGroups(); |
|---|
| 2106 | $this->mEffectiveGroups[] = '*'; |
|---|
| 2107 | if( $this->getId() ) { |
|---|
| 2108 | $this->mEffectiveGroups[] = 'user'; |
|---|
| 2109 | |
|---|
| 2110 | $this->mEffectiveGroups = array_unique( array_merge( |
|---|
| 2111 | $this->mEffectiveGroups, |
|---|
| 2112 | Autopromote::getAutopromoteGroups( $this ) |
|---|
| 2113 | ) ); |
|---|
| 2114 | |
|---|
| 2115 | # Hook for additional groups |
|---|
| 2116 | wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) ); |
|---|
| 2117 | } |
|---|
| 2118 | wfProfileOut( __METHOD__ ); |
|---|
| 2119 | } |
|---|
| 2120 | return $this->mEffectiveGroups; |
|---|
| 2121 | } |
|---|
| 2122 | |
|---|
| 2123 | /** |
|---|
| 2124 | * Get the user's edit count. |
|---|
| 2125 | * @return \int User'e edit count |
|---|
| 2126 | */ |
|---|
| 2127 | function getEditCount() { |
|---|
| 2128 | if( $this->getId() ) { |
|---|
| 2129 | if ( !isset( $this->mEditCount ) ) { |
|---|
| 2130 | /* Populate the count, if it has not been populated yet */ |
|---|
| 2131 | $this->mEditCount = User::edits( $this->mId ); |
|---|
| 2132 | } |
|---|
| 2133 | return $this->mEditCount; |
|---|
| 2134 | } else { |
|---|
| 2135 | /* nil */ |
|---|
| 2136 | return null; |
|---|
| 2137 | } |
|---|
| 2138 | } |
|---|
| 2139 | |
|---|
| 2140 | /** |
|---|
| 2141 | * Add the user to the given group. |
|---|
| 2142 | * This takes immediate effect. |
|---|
| 2143 | * @param $group \string Name of the group to add |
|---|
| 2144 | */ |
|---|
| 2145 | function addGroup( $group ) { |
|---|
| 2146 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2147 | if( $this->getId() ) { |
|---|
| 2148 | $dbw->insert( 'user_groups', |
|---|
| 2149 | array( |
|---|
| 2150 | 'ug_user' => $this->getID(), |
|---|
| 2151 | 'ug_group' => $group, |
|---|
| 2152 | ), |
|---|
| 2153 | 'User::addGroup', |
|---|
| 2154 | array( 'IGNORE' ) ); |
|---|
| 2155 | } |
|---|
| 2156 | |
|---|
| 2157 | $this->loadGroups(); |
|---|
| 2158 | $this->mGroups[] = $group; |
|---|
| 2159 | $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) ); |
|---|
| 2160 | |
|---|
| 2161 | $this->invalidateCache(); |
|---|
| 2162 | } |
|---|
| 2163 | |
|---|
| 2164 | /** |
|---|
| 2165 | * Remove the user from the given group. |
|---|
| 2166 | * This takes immediate effect. |
|---|
| 2167 | * @param $group \string Name of the group to remove |
|---|
| 2168 | */ |
|---|
| 2169 | function removeGroup( $group ) { |
|---|
| 2170 | $this->load(); |
|---|
| 2171 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2172 | $dbw->delete( 'user_groups', |
|---|
| 2173 | array( |
|---|
| 2174 | 'ug_user' => $this->getID(), |
|---|
| 2175 | 'ug_group' => $group, |
|---|
| 2176 | ), |
|---|
| 2177 | 'User::removeGroup' ); |
|---|
| 2178 | |
|---|
| 2179 | $this->loadGroups(); |
|---|
| 2180 | $this->mGroups = array_diff( $this->mGroups, array( $group ) ); |
|---|
| 2181 | $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) ); |
|---|
| 2182 | |
|---|
| 2183 | $this->invalidateCache(); |
|---|
| 2184 | } |
|---|
| 2185 | |
|---|
| 2186 | /** |
|---|
| 2187 | * Get whether the user is logged in |
|---|
| 2188 | * @return \bool True or false |
|---|
| 2189 | */ |
|---|
| 2190 | function isLoggedIn() { |
|---|
| 2191 | return $this->getID() != 0; |
|---|
| 2192 | } |
|---|
| 2193 | |
|---|
| 2194 | /** |
|---|
| 2195 | * Get whether the user is anonymous |
|---|
| 2196 | * @return \bool True or false |
|---|
| 2197 | */ |
|---|
| 2198 | function isAnon() { |
|---|
| 2199 | return !$this->isLoggedIn(); |
|---|
| 2200 | } |
|---|
| 2201 | |
|---|
| 2202 | /** |
|---|
| 2203 | * Get whether the user is a bot |
|---|
| 2204 | * @return \bool True or false |
|---|
| 2205 | * @deprecated |
|---|
| 2206 | */ |
|---|
| 2207 | function isBot() { |
|---|
| 2208 | wfDeprecated( __METHOD__ ); |
|---|
| 2209 | return $this->isAllowed( 'bot' ); |
|---|
| 2210 | } |
|---|
| 2211 | |
|---|
| 2212 | /** |
|---|
| 2213 | * Check if user is allowed to access a feature / make an action |
|---|
| 2214 | * @param $action \string action to be checked |
|---|
| 2215 | * @return \bool True if action is allowed, else false |
|---|
| 2216 | */ |
|---|
| 2217 | function isAllowed( $action = '' ) { |
|---|
| 2218 | if ( $action === '' ) |
|---|
| 2219 | return true; // In the spirit of DWIM |
|---|
| 2220 | # Patrolling may not be enabled |
|---|
| 2221 | if( $action === 'patrol' || $action === 'autopatrol' ) { |
|---|
| 2222 | global $wgUseRCPatrol, $wgUseNPPatrol; |
|---|
| 2223 | if( !$wgUseRCPatrol && !$wgUseNPPatrol ) |
|---|
| 2224 | return false; |
|---|
| 2225 | } |
|---|
| 2226 | # Use strict parameter to avoid matching numeric 0 accidentally inserted |
|---|
| 2227 | # by misconfiguration: 0 == 'foo' |
|---|
| 2228 | return in_array( $action, $this->getRights(), true ); |
|---|
| 2229 | } |
|---|
| 2230 | |
|---|
| 2231 | /** |
|---|
| 2232 | * Check whether to enable recent changes patrol features for this user |
|---|
| 2233 | * @return \bool True or false |
|---|
| 2234 | */ |
|---|
| 2235 | public function useRCPatrol() { |
|---|
| 2236 | global $wgUseRCPatrol; |
|---|
| 2237 | return( $wgUseRCPatrol && ( $this->isAllowed( 'patrol' ) || $this->isAllowed( 'patrolmarks' ) ) ); |
|---|
| 2238 | } |
|---|
| 2239 | |
|---|
| 2240 | /** |
|---|
| 2241 | * Check whether to enable new pages patrol features for this user |
|---|
| 2242 | * @return \bool True or false |
|---|
| 2243 | */ |
|---|
| 2244 | public function useNPPatrol() { |
|---|
| 2245 | global $wgUseRCPatrol, $wgUseNPPatrol; |
|---|
| 2246 | return( ( $wgUseRCPatrol || $wgUseNPPatrol ) && ( $this->isAllowed( 'patrol' ) || $this->isAllowed( 'patrolmarks' ) ) ); |
|---|
| 2247 | } |
|---|
| 2248 | |
|---|
| 2249 | /** |
|---|
| 2250 | * Get the current skin, loading it if required, and setting a title |
|---|
| 2251 | * @param $t Title: the title to use in the skin |
|---|
| 2252 | * @return Skin The current skin |
|---|
| 2253 | * @todo FIXME : need to check the old failback system [AV] |
|---|
| 2254 | */ |
|---|
| 2255 | function &getSkin( $t = null ) { |
|---|
| 2256 | if ( !isset( $this->mSkin ) ) { |
|---|
| 2257 | wfProfileIn( __METHOD__ ); |
|---|
| 2258 | |
|---|
| 2259 | global $wgHiddenPrefs; |
|---|
| 2260 | if( !in_array( 'skin', $wgHiddenPrefs ) ) { |
|---|
| 2261 | # get the user skin |
|---|
| 2262 | global $wgRequest; |
|---|
| 2263 | $userSkin = $this->getOption( 'skin' ); |
|---|
| 2264 | $userSkin = $wgRequest->getVal( 'useskin', $userSkin ); |
|---|
| 2265 | } else { |
|---|
| 2266 | # if we're not allowing users to override, then use the default |
|---|
| 2267 | global $wgDefaultSkin; |
|---|
| 2268 | $userSkin = $wgDefaultSkin; |
|---|
| 2269 | } |
|---|
| 2270 | |
|---|
| 2271 | $this->mSkin =& Skin::newFromKey( $userSkin ); |
|---|
| 2272 | wfProfileOut( __METHOD__ ); |
|---|
| 2273 | } |
|---|
| 2274 | if( $t || !$this->mSkin->getTitle() ) { |
|---|
| 2275 | if ( !$t ) { |
|---|
| 2276 | global $wgOut; |
|---|
| 2277 | $t = $wgOut->getTitle(); |
|---|
| 2278 | } |
|---|
| 2279 | $this->mSkin->setTitle( $t ); |
|---|
| 2280 | } |
|---|
| 2281 | return $this->mSkin; |
|---|
| 2282 | } |
|---|
| 2283 | |
|---|
| 2284 | /** |
|---|
| 2285 | * Check the watched status of an article. |
|---|
| 2286 | * @param $title \type{Title} Title of the article to look at |
|---|
| 2287 | * @return \bool True if article is watched |
|---|
| 2288 | */ |
|---|
| 2289 | function isWatched( $title ) { |
|---|
| 2290 | $wl = WatchedItem::fromUserTitle( $this, $title ); |
|---|
| 2291 | return $wl->isWatched(); |
|---|
| 2292 | } |
|---|
| 2293 | |
|---|
| 2294 | /** |
|---|
| 2295 | * Watch an article. |
|---|
| 2296 | * @param $title \type{Title} Title of the article to look at |
|---|
| 2297 | */ |
|---|
| 2298 | function addWatch( $title ) { |
|---|
| 2299 | $wl = WatchedItem::fromUserTitle( $this, $title ); |
|---|
| 2300 | $wl->addWatch(); |
|---|
| 2301 | $this->invalidateCache(); |
|---|
| 2302 | } |
|---|
| 2303 | |
|---|
| 2304 | /** |
|---|
| 2305 | * Stop watching an article. |
|---|
| 2306 | * @param $title \type{Title} Title of the article to look at |
|---|
| 2307 | */ |
|---|
| 2308 | function removeWatch( $title ) { |
|---|
| 2309 | $wl = WatchedItem::fromUserTitle( $this, $title ); |
|---|
| 2310 | $wl->removeWatch(); |
|---|
| 2311 | $this->invalidateCache(); |
|---|
| 2312 | } |
|---|
| 2313 | |
|---|
| 2314 | /** |
|---|
| 2315 | * Clear the user's notification timestamp for the given title. |
|---|
| 2316 | * If e-notif e-mails are on, they will receive notification mails on |
|---|
| 2317 | * the next change of the page if it's watched etc. |
|---|
| 2318 | * @param $title \type{Title} Title of the article to look at |
|---|
| 2319 | */ |
|---|
| 2320 | function clearNotification( &$title ) { |
|---|
| 2321 | global $wgUser, $wgUseEnotif, $wgShowUpdatedMarker; |
|---|
| 2322 | |
|---|
| 2323 | # Do nothing if the database is locked to writes |
|---|
| 2324 | if( wfReadOnly() ) { |
|---|
| 2325 | return; |
|---|
| 2326 | } |
|---|
| 2327 | |
|---|
| 2328 | if( $title->getNamespace() == NS_USER_TALK && |
|---|
| 2329 | $title->getText() == $this->getName() ) { |
|---|
| 2330 | if( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this ) ) ) |
|---|
| 2331 | return; |
|---|
| 2332 | $this->setNewtalk( false ); |
|---|
| 2333 | } |
|---|
| 2334 | |
|---|
| 2335 | if( !$wgUseEnotif && !$wgShowUpdatedMarker ) { |
|---|
| 2336 | return; |
|---|
| 2337 | } |
|---|
| 2338 | |
|---|
| 2339 | if( $this->isAnon() ) { |
|---|
| 2340 | // Nothing else to do... |
|---|
| 2341 | return; |
|---|
| 2342 | } |
|---|
| 2343 | |
|---|
| 2344 | // Only update the timestamp if the page is being watched. |
|---|
| 2345 | // The query to find out if it is watched is cached both in memcached and per-invocation, |
|---|
| 2346 | // and when it does have to be executed, it can be on a slave |
|---|
| 2347 | // If this is the user's newtalk page, we always update the timestamp |
|---|
| 2348 | if( $title->getNamespace() == NS_USER_TALK && |
|---|
| 2349 | $title->getText() == $wgUser->getName() ) |
|---|
| 2350 | { |
|---|
| 2351 | $watched = true; |
|---|
| 2352 | } elseif ( $this->getId() == $wgUser->getId() ) { |
|---|
| 2353 | $watched = $title->userIsWatching(); |
|---|
| 2354 | } else { |
|---|
| 2355 | $watched = true; |
|---|
| 2356 | } |
|---|
| 2357 | |
|---|
| 2358 | // If the page is watched by the user (or may be watched), update the timestamp on any |
|---|
| 2359 | // any matching rows |
|---|
| 2360 | if ( $watched ) { |
|---|
| 2361 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2362 | $dbw->update( 'watchlist', |
|---|
| 2363 | array( /* SET */ |
|---|
| 2364 | 'wl_notificationtimestamp' => null |
|---|
| 2365 | ), array( /* WHERE */ |
|---|
| 2366 | 'wl_title' => $title->getDBkey(), |
|---|
| 2367 | 'wl_namespace' => $title->getNamespace(), |
|---|
| 2368 | 'wl_user' => $this->getID() |
|---|
| 2369 | ), __METHOD__ |
|---|
| 2370 | ); |
|---|
| 2371 | } |
|---|
| 2372 | } |
|---|
| 2373 | |
|---|
| 2374 | /** |
|---|
| 2375 | * Resets all of the given user's page-change notification timestamps. |
|---|
| 2376 | * If e-notif e-mails are on, they will receive notification mails on |
|---|
| 2377 | * the next change of any watched page. |
|---|
| 2378 | * |
|---|
| 2379 | * @param $currentUser \int User ID |
|---|
| 2380 | */ |
|---|
| 2381 | function clearAllNotifications( $currentUser ) { |
|---|
| 2382 | global $wgUseEnotif, $wgShowUpdatedMarker; |
|---|
| 2383 | if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) { |
|---|
| 2384 | $this->setNewtalk( false ); |
|---|
| 2385 | return; |
|---|
| 2386 | } |
|---|
| 2387 | if( $currentUser != 0 ) { |
|---|
| 2388 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2389 | $dbw->update( 'watchlist', |
|---|
| 2390 | array( /* SET */ |
|---|
| 2391 | 'wl_notificationtimestamp' => null |
|---|
| 2392 | ), array( /* WHERE */ |
|---|
| 2393 | 'wl_user' => $currentUser |
|---|
| 2394 | ), __METHOD__ |
|---|
| 2395 | ); |
|---|
| 2396 | # We also need to clear here the "you have new message" notification for the own user_talk page |
|---|
| 2397 | # This is cleared one page view later in Article::viewUpdates(); |
|---|
| 2398 | } |
|---|
| 2399 | } |
|---|
| 2400 | |
|---|
| 2401 | /** |
|---|
| 2402 | * Set this user's options from an encoded string |
|---|
| 2403 | * @param $str \string Encoded options to import |
|---|
| 2404 | * @private |
|---|
| 2405 | */ |
|---|
| 2406 | function decodeOptions( $str ) { |
|---|
| 2407 | if( !$str ) |
|---|
| 2408 | return; |
|---|
| 2409 | |
|---|
| 2410 | $this->mOptionsLoaded = true; |
|---|
| 2411 | $this->mOptionOverrides = array(); |
|---|
| 2412 | |
|---|
| 2413 | $this->mOptions = array(); |
|---|
| 2414 | $a = explode( "\n", $str ); |
|---|
| 2415 | foreach ( $a as $s ) { |
|---|
| 2416 | $m = array(); |
|---|
| 2417 | if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) { |
|---|
| 2418 | $this->mOptions[$m[1]] = $m[2]; |
|---|
| 2419 | $this->mOptionOverrides[$m[1]] = $m[2]; |
|---|
| 2420 | } |
|---|
| 2421 | } |
|---|
| 2422 | } |
|---|
| 2423 | |
|---|
| 2424 | /** |
|---|
| 2425 | * Set a cookie on the user's client. Wrapper for |
|---|
| 2426 | * WebResponse::setCookie |
|---|
| 2427 | * @param $name \string Name of the cookie to set |
|---|
| 2428 | * @param $value \string Value to set |
|---|
| 2429 | * @param $exp \int Expiration time, as a UNIX time value; |
|---|
| 2430 | * if 0 or not specified, use the default $wgCookieExpiration |
|---|
| 2431 | */ |
|---|
| 2432 | protected function setCookie( $name, $value, $exp = 0 ) { |
|---|
| 2433 | global $wgRequest; |
|---|
| 2434 | $wgRequest->response()->setcookie( $name, $value, $exp ); |
|---|
| 2435 | } |
|---|
| 2436 | |
|---|
| 2437 | /** |
|---|
| 2438 | * Clear a cookie on the user's client |
|---|
| 2439 | * @param $name \string Name of the cookie to clear |
|---|
| 2440 | */ |
|---|
| 2441 | protected function clearCookie( $name ) { |
|---|
| 2442 | $this->setCookie( $name, '', time() - 86400 ); |
|---|
| 2443 | } |
|---|
| 2444 | |
|---|
| 2445 | /** |
|---|
| 2446 | * Set the default cookies for this session on the user's client. |
|---|
| 2447 | */ |
|---|
| 2448 | function setCookies() { |
|---|
| 2449 | $this->load(); |
|---|
| 2450 | if ( 0 == $this->mId ) return; |
|---|
| 2451 | $session = array( |
|---|
| 2452 | 'wsUserID' => $this->mId, |
|---|
| 2453 | 'wsToken' => $this->mToken, |
|---|
| 2454 | 'wsUserName' => $this->getName() |
|---|
| 2455 | ); |
|---|
| 2456 | $cookies = array( |
|---|
| 2457 | 'UserID' => $this->mId, |
|---|
| 2458 | 'UserName' => $this->getName(), |
|---|
| 2459 | ); |
|---|
| 2460 | if ( 1 == $this->getOption( 'rememberpassword' ) ) { |
|---|
| 2461 | $cookies['Token'] = $this->mToken; |
|---|
| 2462 | } else { |
|---|
| 2463 | $cookies['Token'] = false; |
|---|
| 2464 | } |
|---|
| 2465 | |
|---|
| 2466 | wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) ); |
|---|
| 2467 | #check for null, since the hook could cause a null value |
|---|
| 2468 | if ( !is_null( $session ) && isset( $_SESSION ) ){ |
|---|
| 2469 | $_SESSION = $session + $_SESSION; |
|---|
| 2470 | } |
|---|
| 2471 | foreach ( $cookies as $name => $value ) { |
|---|
| 2472 | if ( $value === false ) { |
|---|
| 2473 | $this->clearCookie( $name ); |
|---|
| 2474 | } else { |
|---|
| 2475 | $this->setCookie( $name, $value ); |
|---|
| 2476 | } |
|---|
| 2477 | } |
|---|
| 2478 | } |
|---|
| 2479 | |
|---|
| 2480 | /** |
|---|
| 2481 | * Log this user out. |
|---|
| 2482 | */ |
|---|
| 2483 | function logout() { |
|---|
| 2484 | if( wfRunHooks( 'UserLogout', array( &$this ) ) ) { |
|---|
| 2485 | $this->doLogout(); |
|---|
| 2486 | } |
|---|
| 2487 | } |
|---|
| 2488 | |
|---|
| 2489 | /** |
|---|
| 2490 | * Clear the user's cookies and session, and reset the instance cache. |
|---|
| 2491 | * @private |
|---|
| 2492 | * @see logout() |
|---|
| 2493 | */ |
|---|
| 2494 | function doLogout() { |
|---|
| 2495 | $this->clearInstanceCache( 'defaults' ); |
|---|
| 2496 | |
|---|
| 2497 | $_SESSION['wsUserID'] = 0; |
|---|
| 2498 | |
|---|
| 2499 | $this->clearCookie( 'UserID' ); |
|---|
| 2500 | $this->clearCookie( 'Token' ); |
|---|
| 2501 | |
|---|
| 2502 | # Remember when user logged out, to prevent seeing cached pages |
|---|
| 2503 | $this->setCookie( 'LoggedOut', wfTimestampNow(), time() + 86400 ); |
|---|
| 2504 | } |
|---|
| 2505 | |
|---|
| 2506 | /** |
|---|
| 2507 | * Save this user's settings into the database. |
|---|
| 2508 | * @todo Only rarely do all these fields need to be set! |
|---|
| 2509 | */ |
|---|
| 2510 | function saveSettings() { |
|---|
| 2511 | $this->load(); |
|---|
| 2512 | if ( wfReadOnly() ) { return; } |
|---|
| 2513 | if ( 0 == $this->mId ) { return; } |
|---|
| 2514 | |
|---|
| 2515 | $this->mTouched = self::newTouchedTimestamp(); |
|---|
| 2516 | |
|---|
| 2517 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2518 | $dbw->update( 'user', |
|---|
| 2519 | array( /* SET */ |
|---|
| 2520 | 'user_name' => $this->mName, |
|---|
| 2521 | 'user_password' => $this->mPassword, |
|---|
| 2522 | 'user_newpassword' => $this->mNewpassword, |
|---|
| 2523 | 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ), |
|---|
| 2524 | 'user_real_name' => $this->mRealName, |
|---|
| 2525 | 'user_email' => $this->mEmail, |
|---|
| 2526 | 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), |
|---|
| 2527 | 'user_options' => '', |
|---|
| 2528 | 'user_touched' => $dbw->timestamp( $this->mTouched ), |
|---|
| 2529 | 'user_token' => $this->mToken, |
|---|
| 2530 | 'user_email_token' => $this->mEmailToken, |
|---|
| 2531 | 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ), |
|---|
| 2532 | ), array( /* WHERE */ |
|---|
| 2533 | 'user_id' => $this->mId |
|---|
| 2534 | ), __METHOD__ |
|---|
| 2535 | ); |
|---|
| 2536 | |
|---|
| 2537 | $this->saveOptions(); |
|---|
| 2538 | |
|---|
| 2539 | wfRunHooks( 'UserSaveSettings', array( $this ) ); |
|---|
| 2540 | $this->clearSharedCache(); |
|---|
| 2541 | $this->getUserPage()->invalidateCache(); |
|---|
| 2542 | } |
|---|
| 2543 | |
|---|
| 2544 | /** |
|---|
| 2545 | * If only this user's username is known, and it exists, return the user ID. |
|---|
| 2546 | */ |
|---|
| 2547 | function idForName() { |
|---|
| 2548 | $s = trim( $this->getName() ); |
|---|
| 2549 | if ( $s === '' ) return 0; |
|---|
| 2550 | |
|---|
| 2551 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 2552 | $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__ ); |
|---|
| 2553 | if ( $id === false ) { |
|---|
| 2554 | $id = 0; |
|---|
| 2555 | } |
|---|
| 2556 | return $id; |
|---|
| 2557 | } |
|---|
| 2558 | |
|---|
| 2559 | /** |
|---|
| 2560 | * Add a user to the database, return the user object |
|---|
| 2561 | * |
|---|
| 2562 | * @param $name \string Username to add |
|---|
| 2563 | * @param $params \type{\arrayof{\string}} Non-default parameters to save to the database: |
|---|
| 2564 | * - password The user's password. Password logins will be disabled if this is omitted. |
|---|
| 2565 | * - newpassword A temporary password mailed to the user |
|---|
| 2566 | * - email The user's email address |
|---|
| 2567 | * - email_authenticated The email authentication timestamp |
|---|
| 2568 | * - real_name The user's real name |
|---|
| 2569 | * - options An associative array of non-default options |
|---|
| 2570 | * - token Random authentication token. Do not set. |
|---|
| 2571 | * - registration Registration timestamp. Do not set. |
|---|
| 2572 | * |
|---|
| 2573 | * @return \type{User} A new User object, or null if the username already exists |
|---|
| 2574 | */ |
|---|
| 2575 | static function createNew( $name, $params = array() ) { |
|---|
| 2576 | $user = new User; |
|---|
| 2577 | $user->load(); |
|---|
| 2578 | if ( isset( $params['options'] ) ) { |
|---|
| 2579 | $user->mOptions = $params['options'] + (array)$user->mOptions; |
|---|
| 2580 | unset( $params['options'] ); |
|---|
| 2581 | } |
|---|
| 2582 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2583 | $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); |
|---|
| 2584 | $fields = array( |
|---|
| 2585 | 'user_id' => $seqVal, |
|---|
| 2586 | 'user_name' => $name, |
|---|
| 2587 | 'user_password' => $user->mPassword, |
|---|
| 2588 | 'user_newpassword' => $user->mNewpassword, |
|---|
| 2589 | 'user_newpass_time' => $dbw->timestamp( $user->mNewpassTime ), |
|---|
| 2590 | 'user_email' => $user->mEmail, |
|---|
| 2591 | 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ), |
|---|
| 2592 | 'user_real_name' => $user->mRealName, |
|---|
| 2593 | 'user_options' => '', |
|---|
| 2594 | 'user_token' => $user->mToken, |
|---|
| 2595 | 'user_registration' => $dbw->timestamp( $user->mRegistration ), |
|---|
| 2596 | 'user_editcount' => 0, |
|---|
| 2597 | ); |
|---|
| 2598 | foreach ( $params as $name => $value ) { |
|---|
| 2599 | $fields["user_$name"] = $value; |
|---|
| 2600 | } |
|---|
| 2601 | $dbw->insert( 'user', $fields, __METHOD__, array( 'IGNORE' ) ); |
|---|
| 2602 | if ( $dbw->affectedRows() ) { |
|---|
| 2603 | $newUser = User::newFromId( $dbw->insertId() ); |
|---|
| 2604 | } else { |
|---|
| 2605 | $newUser = null; |
|---|
| 2606 | } |
|---|
| 2607 | return $newUser; |
|---|
| 2608 | } |
|---|
| 2609 | |
|---|
| 2610 | /** |
|---|
| 2611 | * Add this existing user object to the database |
|---|
| 2612 | */ |
|---|
| 2613 | function addToDatabase() { |
|---|
| 2614 | $this->load(); |
|---|
| 2615 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 2616 | $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); |
|---|
| 2617 | $dbw->insert( 'user', |
|---|
| 2618 | array( |
|---|
| 2619 | 'user_id' => $seqVal, |
|---|
| 2620 | 'user_name' => $this->mName, |
|---|
| 2621 | 'user_password' => $this->mPassword, |
|---|
| 2622 | 'user_newpassword' => $this->mNewpassword, |
|---|
| 2623 | 'user_newpass_time' => $dbw->timestamp( $this->mNewpassTime ), |
|---|
| 2624 | 'user_email' => $this->mEmail, |
|---|
| 2625 | 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), |
|---|
| 2626 | 'user_real_name' => $this->mRealName, |
|---|
| 2627 | 'user_options' => '', |
|---|
| 2628 | 'user_token' => $this->mToken, |
|---|
| 2629 | 'user_registration' => $dbw->timestamp( $this->mRegistration ), |
|---|
| 2630 | 'user_editcount' => 0, |
|---|
| 2631 | ), __METHOD__ |
|---|
| 2632 | ); |
|---|
| 2633 | $this->mId = $dbw->insertId(); |
|---|
| 2634 | |
|---|
| 2635 | // Clear instance cache other than user table data, which is already accurate |
|---|
| 2636 | $this->clearInstanceCache(); |
|---|
| 2637 | |
|---|
| 2638 | $this->saveOptions(); |
|---|
| 2639 | } |
|---|
| 2640 | |
|---|
| 2641 | /** |
|---|
| 2642 | * If this (non-anonymous) user is blocked, block any IP address |
|---|
| 2643 | * they've successfully logged in from. |
|---|
| 2644 | */ |
|---|
| 2645 | function spreadBlock() { |
|---|
| 2646 | wfDebug( __METHOD__ . "()\n" ); |
|---|
| 2647 | $this->load(); |
|---|
| 2648 | if ( $this->mId == 0 ) { |
|---|
| 2649 | return; |
|---|
| 2650 | } |
|---|
| 2651 | |
|---|
| 2652 | $userblock = Block::newFromDB( '', $this->mId ); |
|---|
| 2653 | if ( !$userblock ) { |
|---|
| 2654 | return; |
|---|
| 2655 | } |
|---|
| 2656 | |
|---|
| 2657 | $userblock->doAutoblock( wfGetIP() ); |
|---|
| 2658 | } |
|---|
| 2659 | |
|---|
| 2660 | /** |
|---|
| 2661 | * Generate a string which will be different for any combination of |
|---|
| 2662 | * user options which would produce different parser output. |
|---|
| 2663 | * This will be used as part of the hash key for the parser cache, |
|---|
| 2664 | * so users with the same options can share the same cached data |
|---|
| 2665 | * safely. |
|---|
| 2666 | * |
|---|
| 2667 | * Extensions which require it should install 'PageRenderingHash' hook, |
|---|
| 2668 | * which will give them a chance to modify this key based on their own |
|---|
| 2669 | * settings. |
|---|
| 2670 | * |
|---|
| 2671 | * @return \string Page rendering hash |
|---|
| 2672 | */ |
|---|
| 2673 | function getPageRenderingHash() { |
|---|
| 2674 | global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang; |
|---|
| 2675 | if( $this->mHash ){ |
|---|
| 2676 | return $this->mHash; |
|---|
| 2677 | } |
|---|
| 2678 | |
|---|
| 2679 | // stubthreshold is only included below for completeness, |
|---|
| 2680 | // it will always be 0 when this function is called by parsercache. |
|---|
| 2681 | |
|---|
| 2682 | $confstr = $this->getOption( 'math' ); |
|---|
| 2683 | $confstr .= '!' . $this->getOption( 'stubthreshold' ); |
|---|
| 2684 | if ( $wgUseDynamicDates ) { |
|---|
| 2685 | $confstr .= '!' . $this->getDatePreference(); |
|---|
| 2686 | } |
|---|
| 2687 | $confstr .= '!' . ( $this->getOption( 'numberheadings' ) ? '1' : '' ); |
|---|
| 2688 | $confstr .= '!' . $wgLang->getCode(); |
|---|
| 2689 | $confstr .= '!' . $this->getOption( 'thumbsize' ); |
|---|
| 2690 | // add in language specific options, if any |
|---|
| 2691 | $extra = $wgContLang->getExtraHashOptions(); |
|---|
| 2692 | $confstr .= $extra; |
|---|
| 2693 | |
|---|
| 2694 | $confstr .= $wgRenderHashAppend; |
|---|
| 2695 | |
|---|
| 2696 | // Give a chance for extensions to modify the hash, if they have |
|---|
| 2697 | // extra options or other effects on the parser cache. |
|---|
| 2698 | wfRunHooks( 'PageRenderingHash', array( &$confstr ) ); |
|---|
| 2699 | |
|---|
| 2700 | // Make it a valid memcached key fragment |
|---|
| 2701 | $confstr = str_replace( ' ', '_', $confstr ); |
|---|
| 2702 | $this->mHash = $confstr; |
|---|
| 2703 | return $confstr; |
|---|
| 2704 | } |
|---|
| 2705 | |
|---|
| 2706 | /** |
|---|
| 2707 | * Get whether the user is explicitly blocked from account creation. |
|---|
| 2708 | * @return \bool True if blocked |
|---|
| 2709 | */ |
|---|
| 2710 | function isBlockedFromCreateAccount() { |
|---|
| 2711 | $this->getBlockedStatus(); |
|---|
| 2712 | return $this->mBlock && $this->mBlock->mCreateAccount; |
|---|
| 2713 | } |
|---|
| 2714 | |
|---|
| 2715 | /** |
|---|
| 2716 | * Get whether the user is blocked from using Special:Emailuser. |
|---|
| 2717 | * @return \bool True if blocked |
|---|
| 2718 | */ |
|---|
| 2719 | function isBlockedFromEmailuser() { |
|---|
| 2720 | $this->getBlockedStatus(); |
|---|
| 2721 | return $this->mBlock && $this->mBlock->mBlockEmail; |
|---|
| 2722 | } |
|---|
| 2723 | |
|---|
| 2724 | /** |
|---|
| 2725 | * Get whether the user is allowed to create an account. |
|---|
| 2726 | * @return \bool True if allowed |
|---|
| 2727 | */ |
|---|
| 2728 | function isAllowedToCreateAccount() { |
|---|
| 2729 | return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount(); |
|---|
| 2730 | } |
|---|
| 2731 | |
|---|
| 2732 | /** |
|---|
| 2733 | * @deprecated |
|---|
| 2734 | */ |
|---|
| 2735 | function setLoaded( $loaded ) { |
|---|
| 2736 | wfDeprecated( __METHOD__ ); |
|---|
| 2737 | } |
|---|
| 2738 | |
|---|
| 2739 | /** |
|---|
| 2740 | * Get this user's personal page title. |
|---|
| 2741 | * |
|---|
| 2742 | * @return \type{Title} User's personal page title |
|---|
| 2743 | */ |
|---|
| 2744 | function getUserPage() { |
|---|
| 2745 | return Title::makeTitle( NS_USER, $this->getName() ); |
|---|
| 2746 | } |
|---|
| 2747 | |
|---|
| 2748 | /** |
|---|
| 2749 | * Get this user's talk page title. |
|---|
| 2750 | * |
|---|
| 2751 | * @return \type{Title} User's talk page title |
|---|
| 2752 | */ |
|---|
| 2753 | function getTalkPage() { |
|---|
| 2754 | $title = $this->getUserPage(); |
|---|
| 2755 | return $title->getTalkPage(); |
|---|
| 2756 | } |
|---|
| 2757 | |
|---|
| 2758 | /** |
|---|
| 2759 | * Get the maximum valid user ID. |
|---|
| 2760 | * @return \int User ID |
|---|
| 2761 | * @static |
|---|
| 2762 | */ |
|---|
| 2763 | function getMaxID() { |
|---|
| 2764 | static $res; // cache |
|---|
| 2765 | |
|---|
| 2766 | if ( isset( $res ) ) |
|---|
| 2767 | return $res; |
|---|
| 2768 | else { |
|---|
| 2769 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 2770 | return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' ); |
|---|
| 2771 | } |
|---|
| 2772 | } |
|---|
| 2773 | |
|---|
| 2774 | /** |
|---|
| 2775 | * Determine whether the user is a newbie. Newbies are either |
|---|
| 2776 | * anonymous IPs, or the most recently created accounts. |
|---|
| 2777 | * @return \bool True if the user is a newbie |
|---|
| 2778 | */ |
|---|
| 2779 | function isNewbie() { |
|---|
| 2780 | return !$this->isAllowed( 'autoconfirmed' ); |
|---|
| 2781 | } |
|---|
| 2782 | |
|---|
| 2783 | /** |
|---|
| 2784 | * Check to see if the given clear-text password is one of the accepted passwords |
|---|
| 2785 | * @param $password \string user password. |
|---|
| 2786 | * @return \bool True if the given password is correct, otherwise False. |
|---|
| 2787 | */ |
|---|
| 2788 | function checkPassword( $password ) { |
|---|
| 2789 | global $wgAuth; |
|---|
| 2790 | $this->load(); |
|---|
| 2791 | |
|---|
| 2792 | // Even though we stop people from creating passwords that |
|---|
| 2793 | // are shorter than this, doesn't mean people wont be able |
|---|
| 2794 | // to. Certain authentication plugins do NOT want to save |
|---|
| 2795 | // domain passwords in a mysql database, so we should |
|---|
| 2796 | // check this (incase $wgAuth->strict() is false). |
|---|
| 2797 | if( !$this->isValidPassword( $password ) ) { |
|---|
| 2798 | return false; |
|---|
| 2799 | } |
|---|
| 2800 | |
|---|
| 2801 | if( $wgAuth->authenticate( $this->getName(), $password ) ) { |
|---|
| 2802 | return true; |
|---|
| 2803 | } elseif( $wgAuth->strict() ) { |
|---|
| 2804 | /* Auth plugin doesn't allow local authentication */ |
|---|
| 2805 | return false; |
|---|
| 2806 | } elseif( $wgAuth->strictUserAuth( $this->getName() ) ) { |
|---|
| 2807 | /* Auth plugin doesn't allow local authentication for this user name */ |
|---|
| 2808 | return false; |
|---|
| 2809 | } |
|---|
| 2810 | if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) { |
|---|
| 2811 | return true; |
|---|
| 2812 | } elseif ( function_exists( 'iconv' ) ) { |
|---|
| 2813 | # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted |
|---|
| 2814 | # Check for this with iconv |
|---|
| 2815 | $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password ); |
|---|
| 2816 | if ( self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) ) { |
|---|
| 2817 | return true; |
|---|
| 2818 | } |
|---|
| 2819 | } |
|---|
| 2820 | return false; |
|---|
| 2821 | } |
|---|
| 2822 | |
|---|
| 2823 | /** |
|---|
| 2824 | * Check if the given clear-text password matches the temporary password |
|---|
| 2825 | * sent by e-mail for password reset operations. |
|---|
| 2826 | * @return \bool True if matches, false otherwise |
|---|
| 2827 | */ |
|---|
| 2828 | function checkTemporaryPassword( $plaintext ) { |
|---|
| 2829 | global $wgNewPasswordExpiry; |
|---|
| 2830 | if( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) { |
|---|
| 2831 | $this->load(); |
|---|
| 2832 | $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgNewPasswordExpiry; |
|---|
| 2833 | return ( time() < $expiry ); |
|---|
| 2834 | } else { |
|---|
| 2835 | return false; |
|---|
| 2836 | } |
|---|
| 2837 | } |
|---|
| 2838 | |
|---|
| 2839 | /** |
|---|
| 2840 | * Initialize (if necessary) and return a session token value |
|---|
| 2841 | * which can be used in edit forms to show that the user's |
|---|
| 2842 | * login credentials aren't being hijacked with a foreign form |
|---|
| 2843 | * submission. |
|---|
| 2844 | * |
|---|
| 2845 | * @param $salt \types{\string,\arrayof{\string}} Optional function-specific data for hashing |
|---|
| 2846 | * @return \string The new edit token |
|---|
| 2847 | */ |
|---|
| 2848 | function editToken( $salt = '' ) { |
|---|
| 2849 | if ( $this->isAnon() ) { |
|---|
| 2850 | return EDIT_TOKEN_SUFFIX; |
|---|
| 2851 | } else { |
|---|
| 2852 | if( !isset( $_SESSION['wsEditToken'] ) ) { |
|---|
| 2853 | $token = self::generateToken(); |
|---|
| 2854 | $_SESSION['wsEditToken'] = $token; |
|---|
| 2855 | } else { |
|---|
| 2856 | $token = $_SESSION['wsEditToken']; |
|---|
| 2857 | } |
|---|
| 2858 | if( is_array( $salt ) ) { |
|---|
| 2859 | $salt = implode( '|', $salt ); |
|---|
| 2860 | } |
|---|
| 2861 | return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX; |
|---|
| 2862 | } |
|---|
| 2863 | } |
|---|
| 2864 | |
|---|
| 2865 | /** |
|---|
| 2866 | * Generate a looking random token for various uses. |
|---|
| 2867 | * |
|---|
| 2868 | * @param $salt \string Optional salt value |
|---|
| 2869 | * @return \string The new random token |
|---|
| 2870 | */ |
|---|
| 2871 | public static function generateToken( $salt = '' ) { |
|---|
| 2872 | $token = dechex( mt_rand() ) . dechex( mt_rand() ); |
|---|
| 2873 | return md5( $token . $salt ); |
|---|
| 2874 | } |
|---|
| 2875 | |
|---|
| 2876 | /** |
|---|
| 2877 | * Check given value against the token value stored in the session. |
|---|
| 2878 | * A match should confirm that the form was submitted from the |
|---|
| 2879 | * user's own login session, not a form submission from a third-party |
|---|
| 2880 | * site. |
|---|
| 2881 | * |
|---|
| 2882 | * @param $val \string Input value to compare |
|---|
| 2883 | * @param $salt \string Optional function-specific data for hashing |
|---|
| 2884 | * @return \bool Whether the token matches |
|---|
| 2885 | */ |
|---|
| 2886 | function matchEditToken( $val, $salt = '' ) { |
|---|
| 2887 | $sessionToken = $this->editToken( $salt ); |
|---|
| 2888 | if ( $val != $sessionToken ) { |
|---|
| 2889 | wfDebug( "User::matchEditToken: broken session data\n" ); |
|---|
| 2890 | } |
|---|
| 2891 | return $val == $sessionToken; |
|---|
| 2892 | } |
|---|
| 2893 | |
|---|
| 2894 | /** |
|---|
| 2895 | * Check given value against the token value stored in the session, |
|---|
| 2896 | * ignoring the suffix. |
|---|
| 2897 | * |
|---|
| 2898 | * @param $val \string Input value to compare |
|---|
| 2899 | * @param $salt \string Optional function-specific data for hashing |
|---|
| 2900 | * @return \bool Whether the token matches |
|---|
| 2901 | */ |
|---|
| 2902 | function matchEditTokenNoSuffix( $val, $salt = '' ) { |
|---|
| 2903 | $sessionToken = $this->editToken( $salt ); |
|---|
| 2904 | return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 ); |
|---|
| 2905 | } |
|---|
| 2906 | |
|---|
| 2907 | /** |
|---|
| 2908 | * Generate a new e-mail confirmation token and send a confirmation/invalidation |
|---|
| 2909 | * mail to the user's given address. |
|---|
| 2910 | * |
|---|
| 2911 | * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure. |
|---|
| 2912 | */ |
|---|
| 2913 | function sendConfirmationMail() { |
|---|
| 2914 | global $wgLang; |
|---|
| 2915 | $expiration = null; // gets passed-by-ref and defined in next line. |
|---|
| 2916 | $token = $this->confirmationToken( $expiration ); |
|---|
| 2917 | $url = $this->confirmationTokenUrl( $token ); |
|---|
| 2918 | $invalidateURL = $this->invalidationTokenUrl( $token ); |
|---|
| 2919 | $this->saveSettings(); |
|---|
| 2920 | |
|---|
| 2921 | return $this->sendMail( wfMsg( 'confirmemail_subject' ), |
|---|
| 2922 | wfMsg( 'confirmemail_body', |
|---|
| 2923 | wfGetIP(), |
|---|
| 2924 | $this->getName(), |
|---|
| 2925 | $url, |
|---|
| 2926 | $wgLang->timeanddate( $expiration, false ), |
|---|
| 2927 | $invalidateURL, |
|---|
| 2928 | $wgLang->date( $expiration, false ), |
|---|
| 2929 | $wgLang->time( $expiration, false ) ) ); |
|---|
| 2930 | } |
|---|
| 2931 | |
|---|
| 2932 | /** |
|---|
| 2933 | * Send an e-mail to this user's account. Does not check for |
|---|
| 2934 | * confirmed status or validity. |
|---|
| 2935 | * |
|---|
| 2936 | * @param $subject \string Message subject |
|---|
| 2937 | * @param $body \string Message body |
|---|
| 2938 | * @param $from \string Optional From address; if unspecified, default $wgPasswordSender will be used |
|---|
| 2939 | * @param $replyto \string Reply-To address |
|---|
| 2940 | * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure |
|---|
| 2941 | */ |
|---|
| 2942 | function sendMail( $subject, $body, $from = null, $replyto = null ) { |
|---|
| 2943 | if( is_null( $from ) ) { |
|---|
| 2944 | global $wgPasswordSender; |
|---|
| 2945 | $from = $wgPasswordSender; |
|---|
| 2946 | } |
|---|
| 2947 | |
|---|
| 2948 | $to = new MailAddress( $this ); |
|---|
| 2949 | $sender = new MailAddress( $from ); |
|---|
| 2950 | // a hack |
|---|
| 2951 | return UserMailer::send( $to, $sender, $subject, $body, $replyto,'text/html;charset=utf-8' ); |
|---|
| 2952 | //return UserMailer::send( $to, $sender, $subject, $body, $replyto ); |
|---|
| 2953 | } |
|---|
| 2954 | |
|---|
| 2955 | /** |
|---|
| 2956 | * Generate, store, and return a new e-mail confirmation code. |
|---|
| 2957 | * A hash (unsalted, since it's used as a key) is stored. |
|---|
| 2958 | * |
|---|
| 2959 | * @note Call saveSettings() after calling this function to commit |
|---|
| 2960 | * this change to the database. |
|---|
| 2961 | * |
|---|
| 2962 | * @param[out] &$expiration \mixed Accepts the expiration time |
|---|
| 2963 | * @return \string New token |
|---|
| 2964 | * @private |
|---|
| 2965 | */ |
|---|
| 2966 | function confirmationToken( &$expiration ) { |
|---|
| 2967 | $now = time(); |
|---|
| 2968 | $expires = $now + 7 * 24 * 60 * 60; |
|---|
| 2969 | $expiration = wfTimestamp( TS_MW, $expires ); |
|---|
| 2970 | $token = self::generateToken( $this->mId . $this->mEmail . $expires ); |
|---|
| 2971 | $hash = md5( $token ); |
|---|
| 2972 | $this->load(); |
|---|
| 2973 | $this->mEmailToken = $hash; |
|---|
| 2974 | $this->mEmailTokenExpires = $expiration; |
|---|
| 2975 | return $token; |
|---|
| 2976 | } |
|---|
| 2977 | |
|---|
| 2978 | /** |
|---|
| 2979 | * Return a URL the user can use to confirm their email address. |
|---|
| 2980 | * @param $token \string Accepts the email confirmation token |
|---|
| 2981 | * @return \string New token URL |
|---|
| 2982 | * @private |
|---|
| 2983 | */ |
|---|
| 2984 | function confirmationTokenUrl( $token ) { |
|---|
| 2985 | return $this->getTokenUrl( 'ConfirmEmail', $token ); |
|---|
| 2986 | } |
|---|
| 2987 | |
|---|
| 2988 | /** |
|---|
| 2989 | * Return a URL the user can use to invalidate their email address. |
|---|
| 2990 | * @param $token \string Accepts the email confirmation token |
|---|
| 2991 | * @return \string New token URL |
|---|
| 2992 | * @private |
|---|
| 2993 | */ |
|---|
| 2994 | function invalidationTokenUrl( $token ) { |
|---|
| 2995 | return $this->getTokenUrl( 'Invalidateemail', $token ); |
|---|
| 2996 | } |
|---|
| 2997 | |
|---|
| 2998 | /** |
|---|
| 2999 | * Internal function to format the e-mail validation/invalidation URLs. |
|---|
| 3000 | * This uses $wgArticlePath directly as a quickie hack to use the |
|---|
| 3001 | * hardcoded English names of the Special: pages, for ASCII safety. |
|---|
| 3002 | * |
|---|
| 3003 | * @note Since these URLs get dropped directly into emails, using the |
|---|
| 3004 | * short English names avoids insanely long URL-encoded links, which |
|---|
| 3005 | * also sometimes can get corrupted in some browsers/mailers |
|---|
| 3006 | * (bug 6957 with Gmail and Internet Explorer). |
|---|
| 3007 | * |
|---|
| 3008 | * @param $page \string Special page |
|---|
| 3009 | * @param $token \string Token |
|---|
| 3010 | * @return \string Formatted URL |
|---|
| 3011 | */ |
|---|
| 3012 | protected function getTokenUrl( $page, $token ) { |
|---|
| 3013 | global $wgArticlePath; |
|---|
| 3014 | return wfExpandUrl( |
|---|
| 3015 | str_replace( |
|---|
| 3016 | '$1', |
|---|
| 3017 | "Special:$page/$token", |
|---|
| 3018 | $wgArticlePath ) ); |
|---|
| 3019 | } |
|---|
| 3020 | |
|---|
| 3021 | /** |
|---|
| 3022 | * Mark the e-mail address confirmed. |
|---|
| 3023 | * |
|---|
| 3024 | * @note Call saveSettings() after calling this function to commit the change. |
|---|
| 3025 | */ |
|---|
| 3026 | function confirmEmail() { |
|---|
| 3027 | $this->setEmailAuthenticationTimestamp( wfTimestampNow() ); |
|---|
| 3028 | wfRunHooks( 'ConfirmEmailComplete', array( $this ) ); |
|---|
| 3029 | return true; |
|---|
| 3030 | } |
|---|
| 3031 | |
|---|
| 3032 | /** |
|---|
| 3033 | * Invalidate the user's e-mail confirmation, and unauthenticate the e-mail |
|---|
| 3034 | * address if it was already confirmed. |
|---|
| 3035 | * |
|---|
| 3036 | * @note Call saveSettings() after calling this function to commit the change. |
|---|
| 3037 | */ |
|---|
| 3038 | function invalidateEmail() { |
|---|
| 3039 | $this->load(); |
|---|
| 3040 | $this->mEmailToken = null; |
|---|
| 3041 | $this->mEmailTokenExpires = null; |
|---|
| 3042 | $this->setEmailAuthenticationTimestamp( null ); |
|---|
| 3043 | wfRunHooks( 'InvalidateEmailComplete', array( $this ) ); |
|---|
| 3044 | return true; |
|---|
| 3045 | } |
|---|
| 3046 | |
|---|
| 3047 | /** |
|---|
| 3048 | * Set the e-mail authentication timestamp. |
|---|
| 3049 | * @param $timestamp \string TS_MW timestamp |
|---|
| 3050 | */ |
|---|
| 3051 | function setEmailAuthenticationTimestamp( $timestamp ) { |
|---|
| 3052 | $this->load(); |
|---|
| 3053 | $this->mEmailAuthenticated = $timestamp; |
|---|
| 3054 | wfRunHooks( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) ); |
|---|
| 3055 | } |
|---|
| 3056 | |
|---|
| 3057 | /** |
|---|
| 3058 | * Is this user allowed to send e-mails within limits of current |
|---|
| 3059 | * site configuration? |
|---|
| 3060 | * @return \bool True if allowed |
|---|
| 3061 | */ |
|---|
| 3062 | function canSendEmail() { |
|---|
| 3063 | global $wgEnableEmail, $wgEnableUserEmail; |
|---|
| 3064 | if( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) { |
|---|
| 3065 | return false; |
|---|
| 3066 | } |
|---|
| 3067 | $canSend = $this->isEmailConfirmed(); |
|---|
| 3068 | wfRunHooks( 'UserCanSendEmail', array( &$this, &$canSend ) ); |
|---|
| 3069 | return $canSend; |
|---|
| 3070 | } |
|---|
| 3071 | |
|---|
| 3072 | /** |
|---|
| 3073 | * Is this user allowed to receive e-mails within limits of current |
|---|
| 3074 | * site configuration? |
|---|
| 3075 | * @return \bool True if allowed |
|---|
| 3076 | */ |
|---|
| 3077 | function canReceiveEmail() { |
|---|
| 3078 | return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' ); |
|---|
| 3079 | } |
|---|
| 3080 | |
|---|
| 3081 | /** |
|---|
| 3082 | * Is this user's e-mail address valid-looking and confirmed within |
|---|
| 3083 | * limits of the current site configuration? |
|---|
| 3084 | * |
|---|
| 3085 | * @note If $wgEmailAuthentication is on, this may require the user to have |
|---|
| 3086 | * confirmed their address by returning a code or using a password |
|---|
| 3087 | * sent to the address from the wiki. |
|---|
| 3088 | * |
|---|
| 3089 | * @return \bool True if confirmed |
|---|
| 3090 | */ |
|---|
| 3091 | function isEmailConfirmed() { |
|---|
| 3092 | global $wgEmailAuthentication; |
|---|
| 3093 | $this->load(); |
|---|
| 3094 | $confirmed = true; |
|---|
| 3095 | if( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) { |
|---|
| 3096 | if( $this->isAnon() ) |
|---|
| 3097 | return false; |
|---|
| 3098 | if( !self::isValidEmailAddr( $this->mEmail ) ) |
|---|
| 3099 | return false; |
|---|
| 3100 | if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) |
|---|
| 3101 | return false; |
|---|
| 3102 | return true; |
|---|
| 3103 | } else { |
|---|
| 3104 | return $confirmed; |
|---|
| 3105 | } |
|---|
| 3106 | } |
|---|
| 3107 | |
|---|
| 3108 | /** |
|---|
| 3109 | * Check whether there is an outstanding request for e-mail confirmation. |
|---|
| 3110 | * @return \bool True if pending |
|---|
| 3111 | */ |
|---|
| 3112 | function isEmailConfirmationPending() { |
|---|
| 3113 | global $wgEmailAuthentication; |
|---|
| 3114 | return $wgEmailAuthentication && |
|---|
| 3115 | !$this->isEmailConfirmed() && |
|---|
| 3116 | $this->mEmailToken && |
|---|
| 3117 | $this->mEmailTokenExpires > wfTimestamp(); |
|---|
| 3118 | } |
|---|
| 3119 | |
|---|
| 3120 | /** |
|---|
| 3121 | * Get the timestamp of account creation. |
|---|
| 3122 | * |
|---|
| 3123 | * @return \types{\string,\bool} string Timestamp of account creation, or false for |
|---|
| 3124 | * non-existent/anonymous user accounts. |
|---|
| 3125 | */ |
|---|
| 3126 | public function getRegistration() { |
|---|
| 3127 | return $this->getId() > 0 |
|---|
| 3128 | ? $this->mRegistration |
|---|
| 3129 | : false; |
|---|
| 3130 | } |
|---|
| 3131 | |
|---|
| 3132 | /** |
|---|
| 3133 | * Get the timestamp of the first edit |
|---|
| 3134 | * |
|---|
| 3135 | * @return \types{\string,\bool} string Timestamp of first edit, or false for |
|---|
| 3136 | * non-existent/anonymous user accounts. |
|---|
| 3137 | */ |
|---|
| 3138 | public function getFirstEditTimestamp() { |
|---|
| 3139 | if( $this->getId() == 0 ) return false; // anons |
|---|
| 3140 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 3141 | $time = $dbr->selectField( 'revision', 'rev_timestamp', |
|---|
| 3142 | array( 'rev_user' => $this->getId() ), |
|---|
| 3143 | __METHOD__, |
|---|
| 3144 | array( 'ORDER BY' => 'rev_timestamp ASC' ) |
|---|
| 3145 | ); |
|---|
| 3146 | if( !$time ) return false; // no edits |
|---|
| 3147 | return wfTimestamp( TS_MW, $time ); |
|---|
| 3148 | } |
|---|
| 3149 | |
|---|
| 3150 | /** |
|---|
| 3151 | * Get the permissions associated with a given list of groups |
|---|
| 3152 | * |
|---|
| 3153 | * @param $groups \type{\arrayof{\string}} List of internal group names |
|---|
| 3154 | * @return \type{\arrayof{\string}} List of permission key names for given groups combined |
|---|
| 3155 | */ |
|---|
| 3156 | static function getGroupPermissions( $groups ) { |
|---|
| 3157 | global $wgGroupPermissions, $wgRevokePermissions; |
|---|
| 3158 | $rights = array(); |
|---|
| 3159 | // grant every granted permission first |
|---|
| 3160 | foreach( $groups as $group ) { |
|---|
| 3161 | if( isset( $wgGroupPermissions[$group] ) ) { |
|---|
| 3162 | $rights = array_merge( $rights, |
|---|
| 3163 | // array_filter removes empty items |
|---|
| 3164 | array_keys( array_filter( $wgGroupPermissions[$group] ) ) ); |
|---|
| 3165 | } |
|---|
| 3166 | } |
|---|
| 3167 | // now revoke the revoked permissions |
|---|
| 3168 | foreach( $groups as $group ) { |
|---|
| 3169 | if( isset( $wgRevokePermissions[$group] ) ) { |
|---|
| 3170 | $rights = array_diff( $rights, |
|---|
| 3171 | array_keys( array_filter( $wgRevokePermissions[$group] ) ) ); |
|---|
| 3172 | } |
|---|
| 3173 | } |
|---|
| 3174 | return array_unique( $rights ); |
|---|
| 3175 | } |
|---|
| 3176 | |
|---|
| 3177 | /** |
|---|
| 3178 | * Get all the groups who have a given permission |
|---|
| 3179 | * |
|---|
| 3180 | * @param $role \string Role to check |
|---|
| 3181 | * @return \type{\arrayof{\string}} List of internal group names with the given permission |
|---|
| 3182 | */ |
|---|
| 3183 | static function getGroupsWithPermission( $role ) { |
|---|
| 3184 | global $wgGroupPermissions; |
|---|
| 3185 | $allowedGroups = array(); |
|---|
| 3186 | foreach ( $wgGroupPermissions as $group => $rights ) { |
|---|
| 3187 | if ( isset( $rights[$role] ) && $rights[$role] ) { |
|---|
| 3188 | $allowedGroups[] = $group; |
|---|
| 3189 | } |
|---|
| 3190 | } |
|---|
| 3191 | return $allowedGroups; |
|---|
| 3192 | } |
|---|
| 3193 | |
|---|
| 3194 | /** |
|---|
| 3195 | * Get the localized descriptive name for a group, if it exists |
|---|
| 3196 | * |
|---|
| 3197 | * @param $group \string Internal group name |
|---|
| 3198 | * @return \string Localized descriptive group name |
|---|
| 3199 | */ |
|---|
| 3200 | static function getGroupName( $group ) { |
|---|
| 3201 | global $wgMessageCache; |
|---|
| 3202 | $wgMessageCache->loadAllMessages(); |
|---|
| 3203 | $key = "group-$group"; |
|---|
| 3204 | $name = wfMsg( $key ); |
|---|
| 3205 | return $name == '' || wfEmptyMsg( $key, $name ) |
|---|
| 3206 | ? $group |
|---|
| 3207 | : $name; |
|---|
| 3208 | } |
|---|
| 3209 | |
|---|
| 3210 | /** |
|---|
| 3211 | * Get the localized descriptive name for a member of a group, if it exists |
|---|
| 3212 | * |
|---|
| 3213 | * @param $group \string Internal group name |
|---|
| 3214 | * @return \string Localized name for group member |
|---|
| 3215 | */ |
|---|
| 3216 | static function getGroupMember( $group ) { |
|---|
| 3217 | global $wgMessageCache; |
|---|
| 3218 | $wgMessageCache->loadAllMessages(); |
|---|
| 3219 | $key = "group-$group-member"; |
|---|
| 3220 | $name = wfMsg( $key ); |
|---|
| 3221 | return $name == '' || wfEmptyMsg( $key, $name ) |
|---|
| 3222 | ? $group |
|---|
| 3223 | : $name; |
|---|
| 3224 | } |
|---|
| 3225 | |
|---|
| 3226 | /** |
|---|
| 3227 | * Return the set of defined explicit groups. |
|---|
| 3228 | * The implicit groups (by default *, 'user' and 'autoconfirmed') |
|---|
| 3229 | * are not included, as they are defined automatically, not in the database. |
|---|
| 3230 | * @return \type{\arrayof{\string}} Array of internal group names |
|---|
| 3231 | */ |
|---|
| 3232 | static function getAllGroups() { |
|---|
| 3233 | global $wgGroupPermissions, $wgRevokePermissions; |
|---|
| 3234 | return array_diff( |
|---|
| 3235 | array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ), |
|---|
| 3236 | self::getImplicitGroups() |
|---|
| 3237 | ); |
|---|
| 3238 | } |
|---|
| 3239 | |
|---|
| 3240 | /** |
|---|
| 3241 | * Get a list of all available permissions. |
|---|
| 3242 | * @return \type{\arrayof{\string}} Array of permission names |
|---|
| 3243 | */ |
|---|
| 3244 | static function getAllRights() { |
|---|
| 3245 | if ( self::$mAllRights === false ) { |
|---|
| 3246 | global $wgAvailableRights; |
|---|
| 3247 | if ( count( $wgAvailableRights ) ) { |
|---|
| 3248 | self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) ); |
|---|
| 3249 | } else { |
|---|
| 3250 | self::$mAllRights = self::$mCoreRights; |
|---|
| 3251 | } |
|---|
| 3252 | wfRunHooks( 'UserGetAllRights', array( &self::$mAllRights ) ); |
|---|
| 3253 | } |
|---|
| 3254 | return self::$mAllRights; |
|---|
| 3255 | } |
|---|
| 3256 | |
|---|
| 3257 | /** |
|---|
| 3258 | * Get a list of implicit groups |
|---|
| 3259 | * @return \type{\arrayof{\string}} Array of internal group names |
|---|
| 3260 | */ |
|---|
| 3261 | public static function getImplicitGroups() { |
|---|
| 3262 | global $wgImplicitGroups; |
|---|
| 3263 | $groups = $wgImplicitGroups; |
|---|
| 3264 | wfRunHooks( 'UserGetImplicitGroups', array( &$groups ) ); #deprecated, use $wgImplictGroups instead |
|---|
| 3265 | return $groups; |
|---|
| 3266 | } |
|---|
| 3267 | |
|---|
| 3268 | /** |
|---|
| 3269 | * Get the title of a page describing a particular group |
|---|
| 3270 | * |
|---|
| 3271 | * @param $group \string Internal group name |
|---|
| 3272 | * @return \types{\type{Title},\bool} Title of the page if it exists, false otherwise |
|---|
| 3273 | */ |
|---|
| 3274 | static function getGroupPage( $group ) { |
|---|
| 3275 | global $wgMessageCache; |
|---|
| 3276 | $wgMessageCache->loadAllMessages(); |
|---|
| 3277 | $page = wfMsgForContent( 'grouppage-' . $group ); |
|---|
| 3278 | if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) { |
|---|
| 3279 | $title = Title::newFromText( $page ); |
|---|
| 3280 | if( is_object( $title ) ) |
|---|
| 3281 | return $title; |
|---|
| 3282 | } |
|---|
| 3283 | return false; |
|---|
| 3284 | } |
|---|
| 3285 | |
|---|
| 3286 | /** |
|---|
| 3287 | * Create a link to the group in HTML, if available; |
|---|
| 3288 | * else return the group name. |
|---|
| 3289 | * |
|---|
| 3290 | * @param $group \string Internal name of the group |
|---|
| 3291 | * @param $text \string The text of the link |
|---|
| 3292 | * @return \string HTML link to the group |
|---|
| 3293 | */ |
|---|
| 3294 | static function makeGroupLinkHTML( $group, $text = '' ) { |
|---|
| 3295 | if( $text == '' ) { |
|---|
| 3296 | $text = self::getGroupName( $group ); |
|---|
| 3297 | } |
|---|
| 3298 | $title = self::getGroupPage( $group ); |
|---|
| 3299 | if( $title ) { |
|---|
| 3300 | global $wgUser; |
|---|
| 3301 | $sk = $wgUser->getSkin(); |
|---|
| 3302 | return $sk->link( $title, htmlspecialchars( $text ) ); |
|---|
| 3303 | } else { |
|---|
| 3304 | return $text; |
|---|
| 3305 | } |
|---|
| 3306 | } |
|---|
| 3307 | |
|---|
| 3308 | /** |
|---|
| 3309 | * Create a link to the group in Wikitext, if available; |
|---|
| 3310 | * else return the group name. |
|---|
| 3311 | * |
|---|
| 3312 | * @param $group \string Internal name of the group |
|---|
| 3313 | * @param $text \string The text of the link |
|---|
| 3314 | * @return \string Wikilink to the group |
|---|
| 3315 | */ |
|---|
| 3316 | static function makeGroupLinkWiki( $group, $text = '' ) { |
|---|
| 3317 | if( $text == '' ) { |
|---|
| 3318 | $text = self::getGroupName( $group ); |
|---|
| 3319 | } |
|---|
| 3320 | $title = self::getGroupPage( $group ); |
|---|
| 3321 | if( $title ) { |
|---|
| 3322 | $page = $title->getPrefixedText(); |
|---|
| 3323 | return "[[$page|$text]]"; |
|---|
| 3324 | } else { |
|---|
| 3325 | return $text; |
|---|
| 3326 | } |
|---|
| 3327 | } |
|---|
| 3328 | |
|---|
| 3329 | /** |
|---|
| 3330 | * Returns an array of the groups that a particular group can add/remove. |
|---|
| 3331 | * |
|---|
| 3332 | * @param $group String: the group to check for whether it can add/remove |
|---|
| 3333 | * @return Array array( 'add' => array( addablegroups ), |
|---|
| 3334 | * 'remove' => array( removablegroups ), |
|---|
| 3335 | * 'add-self' => array( addablegroups to self), |
|---|
| 3336 | * 'remove-self' => array( removable groups from self) ) |
|---|
| 3337 | */ |
|---|
| 3338 | static function changeableByGroup( $group ) { |
|---|
| 3339 | global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; |
|---|
| 3340 | |
|---|
| 3341 | $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() ); |
|---|
| 3342 | if( empty( $wgAddGroups[$group] ) ) { |
|---|
| 3343 | // Don't add anything to $groups |
|---|
| 3344 | } elseif( $wgAddGroups[$group] === true ) { |
|---|
| 3345 | // You get everything |
|---|
| 3346 | $groups['add'] = self::getAllGroups(); |
|---|
| 3347 | } elseif( is_array( $wgAddGroups[$group] ) ) { |
|---|
| 3348 | $groups['add'] = $wgAddGroups[$group]; |
|---|
| 3349 | } |
|---|
| 3350 | |
|---|
| 3351 | // Same thing for remove |
|---|
| 3352 | if( empty( $wgRemoveGroups[$group] ) ) { |
|---|
| 3353 | } elseif( $wgRemoveGroups[$group] === true ) { |
|---|
| 3354 | $groups['remove'] = self::getAllGroups(); |
|---|
| 3355 | } elseif( is_array( $wgRemoveGroups[$group] ) ) { |
|---|
| 3356 | $groups['remove'] = $wgRemoveGroups[$group]; |
|---|
| 3357 | } |
|---|
| 3358 | |
|---|
| 3359 | // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility |
|---|
| 3360 | if( empty( $wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) { |
|---|
| 3361 | foreach( $wgGroupsAddToSelf as $key => $value ) { |
|---|
| 3362 | if( is_int( $key ) ) { |
|---|
| 3363 | $wgGroupsAddToSelf['user'][] = $value; |
|---|
| 3364 | } |
|---|
| 3365 | } |
|---|
| 3366 | } |
|---|
| 3367 | |
|---|
| 3368 | if( empty( $wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) { |
|---|
| 3369 | foreach( $wgGroupsRemoveFromSelf as $key => $value ) { |
|---|
| 3370 | if( is_int( $key ) ) { |
|---|
| 3371 | $wgGroupsRemoveFromSelf['user'][] = $value; |
|---|
| 3372 | } |
|---|
| 3373 | } |
|---|
| 3374 | } |
|---|
| 3375 | |
|---|
| 3376 | // Now figure out what groups the user can add to him/herself |
|---|
| 3377 | if( empty( $wgGroupsAddToSelf[$group] ) ) { |
|---|
| 3378 | } elseif( $wgGroupsAddToSelf[$group] === true ) { |
|---|
| 3379 | // No idea WHY this would be used, but it's there |
|---|
| 3380 | $groups['add-self'] = User::getAllGroups(); |
|---|
| 3381 | } elseif( is_array( $wgGroupsAddToSelf[$group] ) ) { |
|---|
| 3382 | $groups['add-self'] = $wgGroupsAddToSelf[$group]; |
|---|
| 3383 | } |
|---|
| 3384 | |
|---|
| 3385 | if( empty( $wgGroupsRemoveFromSelf[$group] ) ) { |
|---|
| 3386 | } elseif( $wgGroupsRemoveFromSelf[$group] === true ) { |
|---|
| 3387 | $groups['remove-self'] = User::getAllGroups(); |
|---|
| 3388 | } elseif( is_array( $wgGroupsRemoveFromSelf[$group] ) ) { |
|---|
| 3389 | $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group]; |
|---|
| 3390 | } |
|---|
| 3391 | |
|---|
| 3392 | return $groups; |
|---|
| 3393 | } |
|---|
| 3394 | |
|---|
| 3395 | /** |
|---|
| 3396 | * Returns an array of groups that this user can add and remove |
|---|
| 3397 | * @return Array array( 'add' => array( addablegroups ), |
|---|
| 3398 | * 'remove' => array( removablegroups ), |
|---|
| 3399 | * 'add-self' => array( addablegroups to self), |
|---|
| 3400 | * 'remove-self' => array( removable groups from self) ) |
|---|
| 3401 | */ |
|---|
| 3402 | function changeableGroups() { |
|---|
| 3403 | if( $this->isAllowed( 'userrights' ) ) { |
|---|
| 3404 | // This group gives the right to modify everything (reverse- |
|---|
| 3405 | // compatibility with old "userrights lets you change |
|---|
| 3406 | // everything") |
|---|
| 3407 | // Using array_merge to make the groups reindexed |
|---|
| 3408 | $all = array_merge( User::getAllGroups() ); |
|---|
| 3409 | return array( |
|---|
| 3410 | 'add' => $all, |
|---|
| 3411 | 'remove' => $all, |
|---|
| 3412 | 'add-self' => array(), |
|---|
| 3413 | 'remove-self' => array() |
|---|
| 3414 | ); |
|---|
| 3415 | } |
|---|
| 3416 | |
|---|
| 3417 | // Okay, it's not so simple, we will have to go through the arrays |
|---|
| 3418 | $groups = array( |
|---|
| 3419 | 'add' => array(), |
|---|
| 3420 | 'remove' => array(), |
|---|
| 3421 | 'add-self' => array(), |
|---|
| 3422 | 'remove-self' => array() |
|---|
| 3423 | ); |
|---|
| 3424 | $addergroups = $this->getEffectiveGroups(); |
|---|
| 3425 | |
|---|
| 3426 | foreach( $addergroups as $addergroup ) { |
|---|
| 3427 | $groups = array_merge_recursive( |
|---|
| 3428 | $groups, $this->changeableByGroup( $addergroup ) |
|---|
| 3429 | ); |
|---|
| 3430 | $groups['add'] = array_unique( $groups['add'] ); |
|---|
| 3431 | $groups['remove'] = array_unique( $groups['remove'] ); |
|---|
| 3432 | $groups['add-self'] = array_unique( $groups['add-self'] ); |
|---|
| 3433 | $groups['remove-self'] = array_unique( $groups['remove-self'] ); |
|---|
| 3434 | } |
|---|
| 3435 | return $groups; |
|---|
| 3436 | } |
|---|
| 3437 | |
|---|
| 3438 | /** |
|---|
| 3439 | * Increment the user's edit-count field. |
|---|
| 3440 | * Will have no effect for anonymous users. |
|---|
| 3441 | */ |
|---|
| 3442 | function incEditCount() { |
|---|
| 3443 | if( !$this->isAnon() ) { |
|---|
| 3444 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 3445 | $dbw->update( 'user', |
|---|
| 3446 | array( 'user_editcount=user_editcount+1' ), |
|---|
| 3447 | array( 'user_id' => $this->getId() ), |
|---|
| 3448 | __METHOD__ ); |
|---|
| 3449 | |
|---|
| 3450 | // Lazy initialization check... |
|---|
| 3451 | if( $dbw->affectedRows() == 0 ) { |
|---|
| 3452 | // Pull from a slave to be less cruel to servers |
|---|
| 3453 | // Accuracy isn't the point anyway here |
|---|
| 3454 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 3455 | $count = $dbr->selectField( 'revision', |
|---|
| 3456 | 'COUNT(rev_user)', |
|---|
| 3457 | array( 'rev_user' => $this->getId() ), |
|---|
| 3458 | __METHOD__ ); |
|---|
| 3459 | |
|---|
| 3460 | // Now here's a goddamn hack... |
|---|
| 3461 | if( $dbr !== $dbw ) { |
|---|
| 3462 | // If we actually have a slave server, the count is |
|---|
| 3463 | // at least one behind because the current transaction |
|---|
| 3464 | // has not been committed and replicated. |
|---|
| 3465 | $count++; |
|---|
| 3466 | } else { |
|---|
| 3467 | // But if DB_SLAVE is selecting the master, then the |
|---|
| 3468 | // count we just read includes the revision that was |
|---|
| 3469 | // just added in the working transaction. |
|---|
| 3470 | } |
|---|
| 3471 | |
|---|
| 3472 | $dbw->update( 'user', |
|---|
| 3473 | array( 'user_editcount' => $count ), |
|---|
| 3474 | array( 'user_id' => $this->getId() ), |
|---|
| 3475 | __METHOD__ ); |
|---|
| 3476 | } |
|---|
| 3477 | } |
|---|
| 3478 | // edit count in user cache too |
|---|
| 3479 | $this->invalidateCache(); |
|---|
| 3480 | } |
|---|
| 3481 | |
|---|
| 3482 | /** |
|---|
| 3483 | * Get the description of a given right |
|---|
| 3484 | * |
|---|
| 3485 | * @param $right \string Right to query |
|---|
| 3486 | * @return \string Localized description of the right |
|---|
| 3487 | */ |
|---|
| 3488 | static function getRightDescription( $right ) { |
|---|
| 3489 | global $wgMessageCache; |
|---|
| 3490 | $wgMessageCache->loadAllMessages(); |
|---|
| 3491 | $key = "right-$right"; |
|---|
| 3492 | $name = wfMsg( $key ); |
|---|
| 3493 | return $name == '' || wfEmptyMsg( $key, $name ) |
|---|
| 3494 | ? $right |
|---|
| 3495 | : $name; |
|---|
| 3496 | } |
|---|
| 3497 | |
|---|
| 3498 | /** |
|---|
| 3499 | * Make an old-style password hash |
|---|
| 3500 | * |
|---|
| 3501 | * @param $password \string Plain-text password |
|---|
| 3502 | * @param $userId \string User ID |
|---|
| 3503 | * @return \string Password hash |
|---|
| 3504 | */ |
|---|
| 3505 | static function oldCrypt( $password, $userId ) { |
|---|
| 3506 | global $wgPasswordSalt; |
|---|
| 3507 | if ( $wgPasswordSalt ) { |
|---|
| 3508 | return md5( $userId . '-' . md5( $password ) ); |
|---|
| 3509 | } else { |
|---|
| 3510 | return md5( $password ); |
|---|
| 3511 | } |
|---|
| 3512 | } |
|---|
| 3513 | |
|---|
| 3514 | /** |
|---|
| 3515 | * Make a new-style password hash |
|---|
| 3516 | * |
|---|
| 3517 | * @param $password \string Plain-text password |
|---|
| 3518 | * @param $salt \string Optional salt, may be random or the user ID. |
|---|
| 3519 | * If unspecified or false, will generate one automatically |
|---|
| 3520 | * @return \string Password hash |
|---|
| 3521 | */ |
|---|
| 3522 | static function crypt( $password, $salt = false ) { |
|---|
| 3523 | global $wgPasswordSalt; |
|---|
| 3524 | |
|---|
| 3525 | $hash = ''; |
|---|
| 3526 | if( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) { |
|---|
| 3527 | return $hash; |
|---|
| 3528 | } |
|---|
| 3529 | |
|---|
| 3530 | if( $wgPasswordSalt ) { |
|---|
| 3531 | if ( $salt === false ) { |
|---|
| 3532 | $salt = substr( wfGenerateToken(), 0, 8 ); |
|---|
| 3533 | } |
|---|
| 3534 | return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) ); |
|---|
| 3535 | } else { |
|---|
| 3536 | return ':A:' . md5( $password ); |
|---|
| 3537 | } |
|---|
| 3538 | } |
|---|
| 3539 | |
|---|
| 3540 | /** |
|---|
| 3541 | * Compare a password hash with a plain-text password. Requires the user |
|---|
| 3542 | * ID if there's a chance that the hash is an old-style hash. |
|---|
| 3543 | * |
|---|
| 3544 | * @param $hash \string Password hash |
|---|
| 3545 | * @param $password \string Plain-text password to compare |
|---|
| 3546 | * @param $userId \string User ID for old-style password salt |
|---|
| 3547 | * @return \bool |
|---|
| 3548 | */ |
|---|
| 3549 | static function comparePasswords( $hash, $password, $userId = false ) { |
|---|
| 3550 | $m = false; |
|---|
| 3551 | $type = substr( $hash, 0, 3 ); |
|---|
| 3552 | |
|---|
| 3553 | $result = false; |
|---|
| 3554 | if( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) { |
|---|
| 3555 | return $result; |
|---|
| 3556 | } |
|---|
| 3557 | |
|---|
| 3558 | if ( $type == ':A:' ) { |
|---|
| 3559 | # Unsalted |
|---|
| 3560 | return md5( $password ) === substr( $hash, 3 ); |
|---|
| 3561 | } elseif ( $type == ':B:' ) { |
|---|
| 3562 | # Salted |
|---|
| 3563 | list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 ); |
|---|
| 3564 | return md5( $salt.'-'.md5( $password ) ) == $realHash; |
|---|
| 3565 | } else { |
|---|
| 3566 | # Old-style |
|---|
| 3567 | return self::oldCrypt( $password, $userId ) === $hash; |
|---|
| 3568 | } |
|---|
| 3569 | } |
|---|
| 3570 | |
|---|
| 3571 | /** |
|---|
| 3572 | * Add a newuser log entry for this user |
|---|
| 3573 | * @param $byEmail Boolean: account made by email? |
|---|
| 3574 | */ |
|---|
| 3575 | public function addNewUserLogEntry( $byEmail = false ) { |
|---|
| 3576 | global $wgUser, $wgNewUserLog; |
|---|
| 3577 | if( empty( $wgNewUserLog ) ) { |
|---|
| 3578 | return true; // disabled |
|---|
| 3579 | } |
|---|
| 3580 | |
|---|
| 3581 | if( $this->getName() == $wgUser->getName() ) { |
|---|
| 3582 | $action = 'create'; |
|---|
| 3583 | $message = ''; |
|---|
| 3584 | } else { |
|---|
| 3585 | $action = 'create2'; |
|---|
| 3586 | $message = $byEmail |
|---|
| 3587 | ? wfMsgForContent( 'newuserlog-byemail' ) |
|---|
| 3588 | : ''; |
|---|
| 3589 | } |
|---|
| 3590 | $log = new LogPage( 'newusers' ); |
|---|
| 3591 | $log->addEntry( |
|---|
| 3592 | $action, |
|---|
| 3593 | $this->getUserPage(), |
|---|
| 3594 | $message, |
|---|
| 3595 | array( $this->getId() ) |
|---|
| 3596 | ); |
|---|
| 3597 | return true; |
|---|
| 3598 | } |
|---|
| 3599 | |
|---|
| 3600 | /** |
|---|
| 3601 | * Add an autocreate newuser log entry for this user |
|---|
| 3602 | * Used by things like CentralAuth and perhaps other authplugins. |
|---|
| 3603 | */ |
|---|
| 3604 | public function addNewUserLogEntryAutoCreate() { |
|---|
| 3605 | global $wgNewUserLog; |
|---|
| 3606 | if( empty( $wgNewUserLog ) ) { |
|---|
| 3607 | return true; // disabled |
|---|
| 3608 | } |
|---|
| 3609 | $log = new LogPage( 'newusers', false ); |
|---|
| 3610 | $log->addEntry( 'autocreate', $this->getUserPage(), '', array( $this->getId() ) ); |
|---|
| 3611 | return true; |
|---|
| 3612 | } |
|---|
| 3613 | |
|---|
| 3614 | protected function loadOptions() { |
|---|
| 3615 | $this->load(); |
|---|
| 3616 | if ( $this->mOptionsLoaded || !$this->getId() ) |
|---|
| 3617 | return; |
|---|
| 3618 | |
|---|
| 3619 | $this->mOptions = self::getDefaultOptions(); |
|---|
| 3620 | |
|---|
| 3621 | // Maybe load from the object |
|---|
| 3622 | if ( !is_null( $this->mOptionOverrides ) ) { |
|---|
| 3623 | wfDebug( "Loading options for user " . $this->getId() . " from override cache.\n" ); |
|---|
| 3624 | foreach( $this->mOptionOverrides as $key => $value ) { |
|---|
| 3625 | $this->mOptions[$key] = $value; |
|---|
| 3626 | } |
|---|
| 3627 | } else { |
|---|
| 3628 | wfDebug( "Loading options for user " . $this->getId() . " from database.\n" ); |
|---|
| 3629 | // Load from database |
|---|
| 3630 | $dbr = wfGetDB( DB_SLAVE ); |
|---|
| 3631 | |
|---|
| 3632 | $res = $dbr->select( |
|---|
| 3633 | 'user_properties', |
|---|
| 3634 | '*', |
|---|
| 3635 | array( 'up_user' => $this->getId() ), |
|---|
| 3636 | __METHOD__ |
|---|
| 3637 | ); |
|---|
| 3638 | |
|---|
| 3639 | while( $row = $dbr->fetchObject( $res ) ) { |
|---|
| 3640 | $this->mOptionOverrides[$row->up_property] = $row->up_value; |
|---|
| 3641 | $this->mOptions[$row->up_property] = $row->up_value; |
|---|
| 3642 | } |
|---|
| 3643 | } |
|---|
| 3644 | |
|---|
| 3645 | $this->mOptionsLoaded = true; |
|---|
| 3646 | |
|---|
| 3647 | wfRunHooks( 'UserLoadOptions', array( $this, &$this->mOptions ) ); |
|---|
| 3648 | } |
|---|
| 3649 | |
|---|
| 3650 | protected function saveOptions() { |
|---|
| 3651 | global $wgAllowPrefChange; |
|---|
| 3652 | |
|---|
| 3653 | $extuser = ExternalUser::newFromUser( $this ); |
|---|
| 3654 | |
|---|
| 3655 | $this->loadOptions(); |
|---|
| 3656 | $dbw = wfGetDB( DB_MASTER ); |
|---|
| 3657 | |
|---|
| 3658 | $insert_rows = array(); |
|---|
| 3659 | |
|---|
| 3660 | $saveOptions = $this->mOptions; |
|---|
| 3661 | |
|---|
| 3662 | // Allow hooks to abort, for instance to save to a global profile. |
|---|
| 3663 | // Reset options to default state before saving. |
|---|
| 3664 | if( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) |
|---|
| 3665 | return; |
|---|
| 3666 | |
|---|
| 3667 | foreach( $saveOptions as $key => $value ) { |
|---|
| 3668 | # Don't bother storing default values |
|---|
| 3669 | if ( ( is_null( self::getDefaultOption( $key ) ) && |
|---|
| 3670 | !( $value === false || is_null($value) ) ) || |
|---|
| 3671 | $value != self::getDefaultOption( $key ) ) { |
|---|
| 3672 | $insert_rows[] = array( |
|---|
| 3673 | 'up_user' => $this->getId(), |
|---|
| 3674 | 'up_property' => $key, |
|---|
| 3675 | 'up_value' => $value, |
|---|
| 3676 | ); |
|---|
| 3677 | } |
|---|
| 3678 | if ( $extuser && isset( $wgAllowPrefChange[$key] ) ) { |
|---|
| 3679 | switch ( $wgAllowPrefChange[$key] ) { |
|---|
| 3680 | case 'local': |
|---|
| 3681 | case 'message': |
|---|
| 3682 | break; |
|---|
| 3683 | case 'semiglobal': |
|---|
| 3684 | case 'global': |
|---|
| 3685 | $extuser->setPref( $key, $value ); |
|---|
| 3686 | } |
|---|
| 3687 | } |
|---|
| 3688 | } |
|---|
| 3689 | |
|---|
| 3690 | $dbw->begin(); |
|---|
| 3691 | $dbw->delete( 'user_properties', array( 'up_user' => $this->getId() ), __METHOD__ ); |
|---|
| 3692 | $dbw->insert( 'user_properties', $insert_rows, __METHOD__ ); |
|---|
| 3693 | $dbw->commit(); |
|---|
| 3694 | } |
|---|
| 3695 | |
|---|
| 3696 | /** |
|---|
| 3697 | * Provide an array of HTML5 attributes to put on an input element |
|---|
| 3698 | * intended for the user to enter a new password. This may include |
|---|
| 3699 | * required, title, and/or pattern, depending on $wgMinimalPasswordLength. |
|---|
| 3700 | * |
|---|
| 3701 | * Do *not* use this when asking the user to enter his current password! |
|---|
| 3702 | * Regardless of configuration, users may have invalid passwords for whatever |
|---|
| 3703 | * reason (e.g., they were set before requirements were tightened up). |
|---|
| 3704 | * Only use it when asking for a new password, like on account creation or |
|---|
| 3705 | * ResetPass. |
|---|
| 3706 | * |
|---|
| 3707 | * Obviously, you still need to do server-side checking. |
|---|
| 3708 | * |
|---|
| 3709 | * @return array Array of HTML attributes suitable for feeding to |
|---|
| 3710 | * Html::element(), directly or indirectly. (Don't feed to Xml::*()! |
|---|
| 3711 | * That will potentially output invalid XHTML 1.0 Transitional, and will |
|---|
| 3712 | * get confused by the boolean attribute syntax used.) |
|---|
| 3713 | */ |
|---|
| 3714 | public static function passwordChangeInputAttribs() { |
|---|
| 3715 | global $wgMinimalPasswordLength; |
|---|
| 3716 | |
|---|
| 3717 | if ( $wgMinimalPasswordLength == 0 ) { |
|---|
| 3718 | return array(); |
|---|
| 3719 | } |
|---|
| 3720 | |
|---|
| 3721 | # Note that the pattern requirement will always be satisfied if the |
|---|
| 3722 | # input is empty, so we need required in all cases. |
|---|
| 3723 | $ret = array( 'required' ); |
|---|
| 3724 | |
|---|
| 3725 | # We can't actually do this right now, because Opera 9.6 will print out |
|---|
| 3726 | # the entered password visibly in its error message! When other |
|---|
| 3727 | # browsers add support for this attribute, or Opera fixes its support, |
|---|
| 3728 | # we can add support with a version check to avoid doing this on Opera |
|---|
| 3729 | # versions where it will be a problem. Reported to Opera as |
|---|
| 3730 | # DSK-262266, but they don't have a public bug tracker for us to follow. |
|---|
| 3731 | /* |
|---|
| 3732 | if ( $wgMinimalPasswordLength > 1 ) { |
|---|
| 3733 | $ret['pattern'] = '.{' . intval( $wgMinimalPasswordLength ) . ',}'; |
|---|
| 3734 | $ret['title'] = wfMsgExt( 'passwordtooshort', 'parsemag', |
|---|
| 3735 | $wgMinimalPasswordLength ); |
|---|
| 3736 | } |
|---|
| 3737 | */ |
|---|
| 3738 | |
|---|
| 3739 | return $ret; |
|---|
| 3740 | } |
|---|
| 3741 | } |
|---|