Replacing var keyword with private / public as we now require PHP5.
[lhc/web/wiklou.git] / includes / User.php
1 <?php
2 /**
3 * See user.txt
4 *
5 * @package MediaWiki
6 */
7
8 /**
9 *
10 */
11 require_once( 'WatchedItem.php' );
12
13 # Number of characters in user_token field
14 define( 'USER_TOKEN_LENGTH', 32 );
15
16 # Serialized record version
17 define( 'MW_USER_VERSION', 3 );
18
19 /**
20 *
21 * @package MediaWiki
22 */
23 class User {
24 /*
25 * When adding a new private variable, dont forget to add it to __sleep()
26 */
27 /**@{{
28 * @private
29 */
30 private $mBlockedby; //!<
31 private $mBlockreason; //!<
32 private $mDataLoaded; //!<
33 private $mEmail; //!<
34 private $mEmailAuthenticated; //!<
35 private $mGroups; //!<
36 private $mHash; //!<
37 private $mId; //!<
38 private $mName; //!<
39 private $mNewpassword; //!<
40 private $mNewtalk; //!<
41 private $mOptions; //!<
42 private $mPassword; //!<
43 private $mRealName; //!<
44 private $mRegistration; //!<
45 private $mRights; //!<
46 private $mSkin; //!<
47 private $mToken; //!<
48 private $mTouched; //!<
49 private $mVersion; //!< serialized version
50 /**@}} */
51
52 /** Constructor using User:loadDefaults() */
53 function User() {
54 $this->loadDefaults();
55 $this->mVersion = MW_USER_VERSION;
56 }
57
58 /**
59 * Static factory method
60 * @param string $name Username, validated by Title:newFromText()
61 * @return User
62 * @static
63 */
64 function newFromName( $name ) {
65 # Force usernames to capital
66 global $wgContLang;
67 $name = $wgContLang->ucfirst( $name );
68
69 # Clean up name according to title rules
70 $t = Title::newFromText( $name );
71 if( is_null( $t ) ) {
72 return null;
73 }
74
75 # Reject various classes of invalid names
76 $canonicalName = $t->getText();
77 global $wgAuth;
78 $canonicalName = $wgAuth->getCanonicalName( $t->getText() );
79
80 if( !User::isValidUserName( $canonicalName ) ) {
81 return null;
82 }
83
84 $u = new User();
85 $u->setName( $canonicalName );
86 $u->setId( $u->idFromName( $canonicalName ) );
87 return $u;
88 }
89
90 /**
91 * Factory method to fetch whichever use has a given email confirmation code.
92 * This code is generated when an account is created or its e-mail address
93 * has changed.
94 *
95 * If the code is invalid or has expired, returns NULL.
96 *
97 * @param string $code
98 * @return User
99 * @static
100 */
101 function newFromConfirmationCode( $code ) {
102 $dbr =& wfGetDB( DB_SLAVE );
103 $name = $dbr->selectField( 'user', 'user_name', array(
104 'user_email_token' => md5( $code ),
105 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
106 ) );
107 if( is_string( $name ) ) {
108 return User::newFromName( $name );
109 } else {
110 return null;
111 }
112 }
113
114 /**
115 * Serialze sleep function, for better cache efficiency and avoidance of
116 * silly "incomplete type" errors when skins are cached. The array should
117 * contain names of private variables (see at top of User.php).
118 */
119 function __sleep() {
120 return array(
121 'mBlockedby',
122 'mBlockreason',
123 'mDataLoaded',
124 'mEmail',
125 'mEmailAuthenticated',
126 'mGroups',
127 'mHash',
128 'mId',
129 'mName',
130 'mNewpassword',
131 'mNewtalk',
132 'mOptions',
133 'mPassword',
134 'mRealName',
135 'mRegistration',
136 'mRights',
137 'mToken',
138 'mTouched',
139 'mVersion',
140 );
141 }
142
143 /**
144 * Get username given an id.
145 * @param integer $id Database user id
146 * @return string Nickname of a user
147 * @static
148 */
149 function whoIs( $id ) {
150 $dbr =& wfGetDB( DB_SLAVE );
151 return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), 'User::whoIs' );
152 }
153
154 /**
155 * Get real username given an id.
156 * @param integer $id Database user id
157 * @return string Realname of a user
158 * @static
159 */
160 function whoIsReal( $id ) {
161 $dbr =& wfGetDB( DB_SLAVE );
162 return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), 'User::whoIsReal' );
163 }
164
165 /**
166 * Get database id given a user name
167 * @param string $name Nickname of a user
168 * @return integer|null Database user id (null: if non existent
169 * @static
170 */
171 function idFromName( $name ) {
172 $fname = "User::idFromName";
173
174 $nt = Title::newFromText( $name );
175 if( is_null( $nt ) ) {
176 # Illegal name
177 return null;
178 }
179 $dbr =& wfGetDB( DB_SLAVE );
180 $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), $fname );
181
182 if ( $s === false ) {
183 return 0;
184 } else {
185 return $s->user_id;
186 }
187 }
188
189 /**
190 * does the string match an anonymous IPv4 address?
191 *
192 * Note: We match \d{1,3}\.\d{1,3}\.\d{1,3}\.xxx as an anonymous IP
193 * address because the usemod software would "cloak" anonymous IP
194 * addresses like this, if we allowed accounts like this to be created
195 * new users could get the old edits of these anonymous users.
196 *
197 * @bug 3631
198 *
199 * @static
200 * @param string $name Nickname of a user
201 * @return bool
202 */
203 function isIP( $name ) {
204 return preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/",$name);
205 /*return preg_match("/^
206 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
207 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
208 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))\.
209 (?:[01]?\d{1,2}|2(:?[0-4]\d|5[0-5]))
210 $/x", $name);*/
211 }
212
213 /**
214 * Is the input a valid username?
215 *
216 * Checks if the input is a valid username, we don't want an empty string,
217 * an IP address, anything that containins slashes (would mess up subpages),
218 * is longer than the maximum allowed username size or doesn't begin with
219 * a capital letter.
220 *
221 * @param string $name
222 * @return bool
223 * @static
224 */
225 function isValidUserName( $name ) {
226 global $wgContLang, $wgMaxNameChars;
227
228 if ( $name == ''
229 || User::isIP( $name )
230 || strpos( $name, '/' ) !== false
231 || strlen( $name ) > $wgMaxNameChars
232 || $name != $wgContLang->ucfirst( $name ) )
233 return false;
234
235 // Ensure that the name can't be misresolved as a different title,
236 // such as with extra namespace keys at the start.
237 $parsed = Title::newFromText( $name );
238 if( is_null( $parsed )
239 || $parsed->getNamespace()
240 || strcmp( $name, $parsed->getPrefixedText() ) )
241 return false;
242
243 // Check an additional blacklist of troublemaker characters.
244 // Should these be merged into the title char list?
245 $unicodeBlacklist = '/[' .
246 '\x{0080}-\x{009f}' . # iso-8859-1 control chars
247 '\x{00a0}' . # non-breaking space
248 '\x{2000}-\x{200f}' . # various whitespace
249 '\x{2028}-\x{202f}' . # breaks and control chars
250 '\x{3000}' . # ideographic space
251 '\x{e000}-\x{f8ff}' . # private use
252 ']/u';
253 if( preg_match( $unicodeBlacklist, $name ) ) {
254 return false;
255 }
256
257 return true;
258 }
259
260 /**
261 * Is the input a valid password?
262 *
263 * @param string $password
264 * @return bool
265 * @static
266 */
267 function isValidPassword( $password ) {
268 global $wgMinimalPasswordLength;
269 return strlen( $password ) >= $wgMinimalPasswordLength;
270 }
271
272 /**
273 * Does the string match roughly an email address ?
274 *
275 * There used to be a regular expression here, it got removed because it
276 * rejected valid addresses. Actually just check if there is '@' somewhere
277 * in the given address.
278 *
279 * @todo Check for RFC 2822 compilance
280 * @bug 959
281 *
282 * @param string $addr email address
283 * @static
284 * @return bool
285 */
286 function isValidEmailAddr ( $addr ) {
287 return ( trim( $addr ) != '' ) &&
288 (false !== strpos( $addr, '@' ) );
289 }
290
291 /**
292 * Count the number of edits of a user
293 *
294 * @param int $uid The user ID to check
295 * @return int
296 */
297 function edits( $uid ) {
298 $fname = 'User::edits';
299
300 $dbr =& wfGetDB( DB_SLAVE );
301 return $dbr->selectField(
302 'revision', 'count(*)',
303 array( 'rev_user' => $uid ),
304 $fname
305 );
306 }
307
308 /**
309 * probably return a random password
310 * @return string probably a random password
311 * @static
312 * @todo Check what is doing really [AV]
313 */
314 function randomPassword() {
315 global $wgMinimalPasswordLength;
316 $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
317 $l = strlen( $pwchars ) - 1;
318
319 $pwlength = max( 7, $wgMinimalPasswordLength );
320 $digit = mt_rand(0, $pwlength - 1);
321 $np = '';
322 for ( $i = 0; $i < $pwlength; $i++ ) {
323 $np .= $i == $digit ? chr( mt_rand(48, 57) ) : $pwchars{ mt_rand(0, $l)};
324 }
325 return $np;
326 }
327
328 /**
329 * Set properties to default
330 * Used at construction. It will load per language default settings only
331 * if we have an available language object.
332 */
333 function loadDefaults() {
334 static $n=0;
335 $n++;
336 $fname = 'User::loadDefaults' . $n;
337 wfProfileIn( $fname );
338
339 global $wgCookiePrefix;
340 global $wgNamespacesToBeSearchedDefault;
341
342 $this->mId = 0;
343 $this->mNewtalk = -1;
344 $this->mName = false;
345 $this->mRealName = $this->mEmail = '';
346 $this->mEmailAuthenticated = null;
347 $this->mPassword = $this->mNewpassword = '';
348 $this->mRights = array();
349 $this->mGroups = array();
350 $this->mOptions = User::getDefaultOptions();
351
352 foreach( $wgNamespacesToBeSearchedDefault as $nsnum => $val ) {
353 $this->mOptions['searchNs'.$nsnum] = $val;
354 }
355 unset( $this->mSkin );
356 $this->mDataLoaded = false;
357 $this->mBlockedby = -1; # Unset
358 $this->setToken(); # Random
359 $this->mHash = false;
360
361 if ( isset( $_COOKIE[$wgCookiePrefix.'LoggedOut'] ) ) {
362 $this->mTouched = wfTimestamp( TS_MW, $_COOKIE[$wgCookiePrefix.'LoggedOut'] );
363 }
364 else {
365 $this->mTouched = '0'; # Allow any pages to be cached
366 }
367
368 $this->mRegistration = wfTimestamp( TS_MW );
369
370 wfProfileOut( $fname );
371 }
372
373 /**
374 * Combine the language default options with any site-specific options
375 * and add the default language variants.
376 *
377 * @return array
378 * @static
379 * @private
380 */
381 function getDefaultOptions() {
382 /**
383 * Site defaults will override the global/language defaults
384 */
385 global $wgContLang, $wgDefaultUserOptions;
386 $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptions();
387
388 /**
389 * default language setting
390 */
391 $variant = $wgContLang->getPreferredVariant();
392 $defOpt['variant'] = $variant;
393 $defOpt['language'] = $variant;
394
395 return $defOpt;
396 }
397
398 /**
399 * Get a given default option value.
400 *
401 * @param string $opt
402 * @return string
403 * @static
404 * @public
405 */
406 function getDefaultOption( $opt ) {
407 $defOpts = User::getDefaultOptions();
408 if( isset( $defOpts[$opt] ) ) {
409 return $defOpts[$opt];
410 } else {
411 return '';
412 }
413 }
414
415 /**
416 * Get blocking information
417 * @private
418 * @param bool $bFromSlave Specify whether to check slave or master. To improve performance,
419 * non-critical checks are done against slaves. Check when actually saving should be done against
420 * master.
421 */
422 function getBlockedStatus( $bFromSlave = true ) {
423 global $wgEnableSorbs, $wgProxyWhitelist;
424
425 if ( -1 != $this->mBlockedby ) {
426 wfDebug( "User::getBlockedStatus: already loaded.\n" );
427 return;
428 }
429
430 $fname = 'User::getBlockedStatus';
431 wfProfileIn( $fname );
432 wfDebug( "$fname: checking...\n" );
433
434 $this->mBlockedby = 0;
435 $ip = wfGetIP();
436
437 # User/IP blocking
438 $block = new Block();
439 $block->fromMaster( !$bFromSlave );
440 if ( $block->load( $ip , $this->mId ) ) {
441 wfDebug( "$fname: Found block.\n" );
442 $this->mBlockedby = $block->mBy;
443 $this->mBlockreason = $block->mReason;
444 if ( $this->isLoggedIn() ) {
445 $this->spreadBlock();
446 }
447 } else {
448 wfDebug( "$fname: No block.\n" );
449 }
450
451 # Proxy blocking
452 # FIXME ? proxyunbannable is to deprecate the old isSysop()
453 if ( !$this->isAllowed('proxyunbannable') && !in_array( $ip, $wgProxyWhitelist ) ) {
454
455 # Local list
456 if ( wfIsLocallyBlockedProxy( $ip ) ) {
457 $this->mBlockedby = wfMsg( 'proxyblocker' );
458 $this->mBlockreason = wfMsg( 'proxyblockreason' );
459 }
460
461 # DNSBL
462 if ( !$this->mBlockedby && $wgEnableSorbs && !$this->getID() ) {
463 if ( $this->inSorbsBlacklist( $ip ) ) {
464 $this->mBlockedby = wfMsg( 'sorbs' );
465 $this->mBlockreason = wfMsg( 'sorbsreason' );
466 }
467 }
468 }
469
470 # Extensions
471 wfRunHooks( 'GetBlockedStatus', array( &$this ) );
472
473 wfProfileOut( $fname );
474 }
475
476 function inSorbsBlacklist( $ip ) {
477 global $wgEnableSorbs;
478 return $wgEnableSorbs &&
479 $this->inDnsBlacklist( $ip, 'http.dnsbl.sorbs.net.' );
480 }
481
482 function inOpmBlacklist( $ip ) {
483 global $wgEnableOpm;
484 return $wgEnableOpm &&
485 $this->inDnsBlacklist( $ip, 'opm.blitzed.org.' );
486 }
487
488 function inDnsBlacklist( $ip, $base ) {
489 $fname = 'User::inDnsBlacklist';
490 wfProfileIn( $fname );
491
492 $found = false;
493 $host = '';
494
495 if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) {
496 # Make hostname
497 for ( $i=4; $i>=1; $i-- ) {
498 $host .= $m[$i] . '.';
499 }
500 $host .= $base;
501
502 # Send query
503 $ipList = gethostbynamel( $host );
504
505 if ( $ipList ) {
506 wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
507 $found = true;
508 } else {
509 wfDebug( "Requested $host, not found in $base.\n" );
510 }
511 }
512
513 wfProfileOut( $fname );
514 return $found;
515 }
516
517 /**
518 * Primitive rate limits: enforce maximum actions per time period
519 * to put a brake on flooding.
520 *
521 * Note: when using a shared cache like memcached, IP-address
522 * last-hit counters will be shared across wikis.
523 *
524 * @return bool true if a rate limiter was tripped
525 * @public
526 */
527 function pingLimiter( $action='edit' ) {
528 global $wgRateLimits;
529 if( !isset( $wgRateLimits[$action] ) ) {
530 return false;
531 }
532 if( $this->isAllowed( 'delete' ) ) {
533 // goddam cabal
534 return false;
535 }
536
537 global $wgMemc, $wgDBname, $wgRateLimitLog;
538 $fname = 'User::pingLimiter';
539 wfProfileIn( $fname );
540
541 $limits = $wgRateLimits[$action];
542 $keys = array();
543 $id = $this->getId();
544 $ip = wfGetIP();
545
546 if( isset( $limits['anon'] ) && $id == 0 ) {
547 $keys["$wgDBname:limiter:$action:anon"] = $limits['anon'];
548 }
549
550 if( isset( $limits['user'] ) && $id != 0 ) {
551 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['user'];
552 }
553 if( $this->isNewbie() ) {
554 if( isset( $limits['newbie'] ) && $id != 0 ) {
555 $keys["$wgDBname:limiter:$action:user:$id"] = $limits['newbie'];
556 }
557 if( isset( $limits['ip'] ) ) {
558 $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
559 }
560 if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
561 $subnet = $matches[1];
562 $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
563 }
564 }
565
566 $triggered = false;
567 foreach( $keys as $key => $limit ) {
568 list( $max, $period ) = $limit;
569 $summary = "(limit $max in {$period}s)";
570 $count = $wgMemc->get( $key );
571 if( $count ) {
572 if( $count > $max ) {
573 wfDebug( "$fname: tripped! $key at $count $summary\n" );
574 if( $wgRateLimitLog ) {
575 @error_log( wfTimestamp( TS_MW ) . ' ' . $wgDBname . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
576 }
577 $triggered = true;
578 } else {
579 wfDebug( "$fname: ok. $key at $count $summary\n" );
580 }
581 } else {
582 wfDebug( "$fname: adding record for $key $summary\n" );
583 $wgMemc->add( $key, 1, intval( $period ) );
584 }
585 $wgMemc->incr( $key );
586 }
587
588 wfProfileOut( $fname );
589 return $triggered;
590 }
591
592 /**
593 * Check if user is blocked
594 * @return bool True if blocked, false otherwise
595 */
596 function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
597 wfDebug( "User::isBlocked: enter\n" );
598 $this->getBlockedStatus( $bFromSlave );
599 return $this->mBlockedby !== 0;
600 }
601
602 /**
603 * Check if user is blocked from editing a particular article
604 */
605 function isBlockedFrom( $title, $bFromSlave = false ) {
606 global $wgBlockAllowsUTEdit;
607 $fname = 'User::isBlockedFrom';
608 wfProfileIn( $fname );
609 wfDebug( "$fname: enter\n" );
610
611 if ( $wgBlockAllowsUTEdit && $title->getText() === $this->getName() &&
612 $title->getNamespace() == NS_USER_TALK )
613 {
614 $blocked = false;
615 wfDebug( "$fname: self-talk page, ignoring any blocks\n" );
616 } else {
617 wfDebug( "$fname: asking isBlocked()\n" );
618 $blocked = $this->isBlocked( $bFromSlave );
619 }
620 wfProfileOut( $fname );
621 return $blocked;
622 }
623
624 /**
625 * Get name of blocker
626 * @return string name of blocker
627 */
628 function blockedBy() {
629 $this->getBlockedStatus();
630 return $this->mBlockedby;
631 }
632
633 /**
634 * Get blocking reason
635 * @return string Blocking reason
636 */
637 function blockedFor() {
638 $this->getBlockedStatus();
639 return $this->mBlockreason;
640 }
641
642 /**
643 * Initialise php session
644 */
645 function SetupSession() {
646 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
647 if( $wgSessionsInMemcached ) {
648 require_once( 'MemcachedSessions.php' );
649 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
650 # If it's left on 'user' or another setting from another
651 # application, it will end up failing. Try to recover.
652 ini_set ( 'session.save_handler', 'files' );
653 }
654 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
655 session_cache_limiter( 'private, must-revalidate' );
656 @session_start();
657 }
658
659 /**
660 * Create a new user object using data from session
661 * @static
662 */
663 function loadFromSession() {
664 global $wgMemc, $wgDBname, $wgCookiePrefix;
665
666 if ( isset( $_SESSION['wsUserID'] ) ) {
667 if ( 0 != $_SESSION['wsUserID'] ) {
668 $sId = $_SESSION['wsUserID'];
669 } else {
670 return new User();
671 }
672 } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
673 $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
674 $_SESSION['wsUserID'] = $sId;
675 } else {
676 return new User();
677 }
678 if ( isset( $_SESSION['wsUserName'] ) ) {
679 $sName = $_SESSION['wsUserName'];
680 } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
681 $sName = $_COOKIE["{$wgCookiePrefix}UserName"];
682 $_SESSION['wsUserName'] = $sName;
683 } else {
684 return new User();
685 }
686
687 $passwordCorrect = FALSE;
688 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
689 if( !is_object( $user ) || $user->mVersion < MW_USER_VERSION ) {
690 # Expire old serialized objects; they may be corrupt.
691 $user = false;
692 }
693 if($makenew = !$user) {
694 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
695 $user = new User();
696 $user->mId = $sId;
697 $user->loadFromDatabase();
698 } else {
699 wfDebug( "User::loadFromSession() got from cache!\n" );
700 }
701
702 if ( isset( $_SESSION['wsToken'] ) ) {
703 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken;
704 } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) {
705 $passwordCorrect = $user->mToken == $_COOKIE["{$wgCookiePrefix}Token"];
706 } else {
707 return new User(); # Can't log in from session
708 }
709
710 if ( ( $sName == $user->mName ) && $passwordCorrect ) {
711 if($makenew) {
712 if($wgMemc->set( $key, $user ))
713 wfDebug( "User::loadFromSession() successfully saved user\n" );
714 else
715 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
716 }
717 return $user;
718 }
719 return new User(); # Can't log in from session
720 }
721
722 /**
723 * Load a user from the database
724 */
725 function loadFromDatabase() {
726 $fname = "User::loadFromDatabase";
727
728 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
729 # loading in a command line script, don't assume all command line scripts need it like this
730 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
731 if ( $this->mDataLoaded ) {
732 return;
733 }
734
735 # Paranoia
736 $this->mId = intval( $this->mId );
737
738 /** Anonymous user */
739 if( !$this->mId ) {
740 /** Get rights */
741 $this->mRights = $this->getGroupPermissions( array( '*' ) );
742 $this->mDataLoaded = true;
743 return;
744 } # the following stuff is for non-anonymous users only
745
746 $dbr =& wfGetDB( DB_SLAVE );
747 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
748 'user_email_authenticated',
749 'user_real_name','user_options','user_touched', 'user_token', 'user_registration' ),
750 array( 'user_id' => $this->mId ), $fname );
751
752 if ( $s !== false ) {
753 $this->mName = $s->user_name;
754 $this->mEmail = $s->user_email;
755 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $s->user_email_authenticated );
756 $this->mRealName = $s->user_real_name;
757 $this->mPassword = $s->user_password;
758 $this->mNewpassword = $s->user_newpassword;
759 $this->decodeOptions( $s->user_options );
760 $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
761 $this->mToken = $s->user_token;
762 $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration );
763
764 $res = $dbr->select( 'user_groups',
765 array( 'ug_group' ),
766 array( 'ug_user' => $this->mId ),
767 $fname );
768 $this->mGroups = array();
769 while( $row = $dbr->fetchObject( $res ) ) {
770 $this->mGroups[] = $row->ug_group;
771 }
772 $implicitGroups = array( '*', 'user' );
773
774 global $wgAutoConfirmAge;
775 $accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration );
776 if( $accountAge >= $wgAutoConfirmAge ) {
777 $implicitGroups[] = 'autoconfirmed';
778 }
779
780 $effectiveGroups = array_merge( $implicitGroups, $this->mGroups );
781 $this->mRights = $this->getGroupPermissions( $effectiveGroups );
782 }
783
784 $this->mDataLoaded = true;
785 }
786
787 function getID() { return $this->mId; }
788 function setID( $v ) {
789 $this->mId = $v;
790 $this->mDataLoaded = false;
791 }
792
793 function getName() {
794 $this->loadFromDatabase();
795 if ( $this->mName === false ) {
796 $this->mName = wfGetIP();
797 }
798 return $this->mName;
799 }
800
801 function setName( $str ) {
802 $this->loadFromDatabase();
803 $this->mName = $str;
804 }
805
806
807 /**
808 * Return the title dbkey form of the name, for eg user pages.
809 * @return string
810 * @public
811 */
812 function getTitleKey() {
813 return str_replace( ' ', '_', $this->getName() );
814 }
815
816 function getNewtalk() {
817 $this->loadFromDatabase();
818
819 # Load the newtalk status if it is unloaded (mNewtalk=-1)
820 if( $this->mNewtalk === -1 ) {
821 $this->mNewtalk = false; # reset talk page status
822
823 # Check memcached separately for anons, who have no
824 # entire User object stored in there.
825 if( !$this->mId ) {
826 global $wgDBname, $wgMemc;
827 $key = "$wgDBname:newtalk:ip:" . $this->getName();
828 $newtalk = $wgMemc->get( $key );
829 if( is_integer( $newtalk ) ) {
830 $this->mNewtalk = (bool)$newtalk;
831 } else {
832 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
833 $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 );
834 }
835 } else {
836 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
837 }
838 }
839
840 return (bool)$this->mNewtalk;
841 }
842
843 /**
844 * Return the talk page(s) this user has new messages on.
845 */
846 function getNewMessageLinks() {
847 global $wgDBname;
848 $talks = array();
849 if (!wfRunHooks('UserRetrieveNewTalks', array(&$this, &$talks)))
850 return $talks;
851
852 if (!$this->getNewtalk())
853 return array();
854 $up = $this->getUserPage();
855 $utp = $up->getTalkPage();
856 return array(array("wiki" => $wgDBname, "link" => $utp->getLocalURL()));
857 }
858
859
860 /**
861 * Perform a user_newtalk check on current slaves; if the memcached data
862 * is funky we don't want newtalk state to get stuck on save, as that's
863 * damn annoying.
864 *
865 * @param string $field
866 * @param mixed $id
867 * @return bool
868 * @private
869 */
870 function checkNewtalk( $field, $id ) {
871 $fname = 'User::checkNewtalk';
872 $dbr =& wfGetDB( DB_SLAVE );
873 $ok = $dbr->selectField( 'user_newtalk', $field,
874 array( $field => $id ), $fname );
875 return $ok !== false;
876 }
877
878 /**
879 * Add or update the
880 * @param string $field
881 * @param mixed $id
882 * @private
883 */
884 function updateNewtalk( $field, $id ) {
885 $fname = 'User::updateNewtalk';
886 if( $this->checkNewtalk( $field, $id ) ) {
887 wfDebug( "$fname already set ($field, $id), ignoring\n" );
888 return false;
889 }
890 $dbw =& wfGetDB( DB_MASTER );
891 $dbw->insert( 'user_newtalk',
892 array( $field => $id ),
893 $fname,
894 'IGNORE' );
895 wfDebug( "$fname: set on ($field, $id)\n" );
896 return true;
897 }
898
899 /**
900 * Clear the new messages flag for the given user
901 * @param string $field
902 * @param mixed $id
903 * @private
904 */
905 function deleteNewtalk( $field, $id ) {
906 $fname = 'User::deleteNewtalk';
907 if( !$this->checkNewtalk( $field, $id ) ) {
908 wfDebug( "$fname: already gone ($field, $id), ignoring\n" );
909 return false;
910 }
911 $dbw =& wfGetDB( DB_MASTER );
912 $dbw->delete( 'user_newtalk',
913 array( $field => $id ),
914 $fname );
915 wfDebug( "$fname: killed on ($field, $id)\n" );
916 return true;
917 }
918
919 /**
920 * Update the 'You have new messages!' status.
921 * @param bool $val
922 */
923 function setNewtalk( $val ) {
924 if( wfReadOnly() ) {
925 return;
926 }
927
928 $this->loadFromDatabase();
929 $this->mNewtalk = $val;
930
931 $fname = 'User::setNewtalk';
932
933 if( $this->isAnon() ) {
934 $field = 'user_ip';
935 $id = $this->getName();
936 } else {
937 $field = 'user_id';
938 $id = $this->getId();
939 }
940
941 if( $val ) {
942 $changed = $this->updateNewtalk( $field, $id );
943 } else {
944 $changed = $this->deleteNewtalk( $field, $id );
945 }
946
947 if( $changed ) {
948 if( $this->isAnon() ) {
949 // Anons have a separate memcached space, since
950 // user records aren't kept for them.
951 global $wgDBname, $wgMemc;
952 $key = "$wgDBname:newtalk:ip:$val";
953 $wgMemc->set( $key, $val ? 1 : 0 );
954 } else {
955 if( $val ) {
956 // Make sure the user page is watched, so a notification
957 // will be sent out if enabled.
958 $this->addWatch( $this->getTalkPage() );
959 }
960 }
961 $this->invalidateCache();
962 $this->saveSettings();
963 }
964 }
965
966 function invalidateCache() {
967 global $wgClockSkewFudge;
968 $this->loadFromDatabase();
969 $this->mTouched = wfTimestamp(TS_MW, time() + $wgClockSkewFudge );
970 # Don't forget to save the options after this or
971 # it won't take effect!
972 }
973
974 function validateCache( $timestamp ) {
975 $this->loadFromDatabase();
976 return ($timestamp >= $this->mTouched);
977 }
978
979 /**
980 * Encrypt a password.
981 * It can eventuall salt a password @see User::addSalt()
982 * @param string $p clear Password.
983 * @return string Encrypted password.
984 */
985 function encryptPassword( $p ) {
986 return wfEncryptPassword( $this->mId, $p );
987 }
988
989 # Set the password and reset the random token
990 function setPassword( $str ) {
991 $this->loadFromDatabase();
992 $this->setToken();
993 $this->mPassword = $this->encryptPassword( $str );
994 $this->mNewpassword = '';
995 }
996
997 # Set the random token (used for persistent authentication)
998 function setToken( $token = false ) {
999 global $wgSecretKey, $wgProxyKey, $wgDBname;
1000 if ( !$token ) {
1001 if ( $wgSecretKey ) {
1002 $key = $wgSecretKey;
1003 } elseif ( $wgProxyKey ) {
1004 $key = $wgProxyKey;
1005 } else {
1006 $key = microtime();
1007 }
1008 $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId );
1009 } else {
1010 $this->mToken = $token;
1011 }
1012 }
1013
1014
1015 function setCookiePassword( $str ) {
1016 $this->loadFromDatabase();
1017 $this->mCookiePassword = md5( $str );
1018 }
1019
1020 function setNewpassword( $str ) {
1021 $this->loadFromDatabase();
1022 $this->mNewpassword = $this->encryptPassword( $str );
1023 }
1024
1025 function getEmail() {
1026 $this->loadFromDatabase();
1027 return $this->mEmail;
1028 }
1029
1030 function getEmailAuthenticationTimestamp() {
1031 $this->loadFromDatabase();
1032 return $this->mEmailAuthenticated;
1033 }
1034
1035 function setEmail( $str ) {
1036 $this->loadFromDatabase();
1037 $this->mEmail = $str;
1038 }
1039
1040 function getRealName() {
1041 $this->loadFromDatabase();
1042 return $this->mRealName;
1043 }
1044
1045 function setRealName( $str ) {
1046 $this->loadFromDatabase();
1047 $this->mRealName = $str;
1048 }
1049
1050 /**
1051 * @param string $oname The option to check
1052 * @return string
1053 */
1054 function getOption( $oname ) {
1055 $this->loadFromDatabase();
1056 if ( array_key_exists( $oname, $this->mOptions ) ) {
1057 return trim( $this->mOptions[$oname] );
1058 } else {
1059 return '';
1060 }
1061 }
1062
1063 /**
1064 * @param string $oname The option to check
1065 * @return bool False if the option is not selected, true if it is
1066 */
1067 function getBoolOption( $oname ) {
1068 return (bool)$this->getOption( $oname );
1069 }
1070
1071 function setOption( $oname, $val ) {
1072 $this->loadFromDatabase();
1073 if ( $oname == 'skin' ) {
1074 # Clear cached skin, so the new one displays immediately in Special:Preferences
1075 unset( $this->mSkin );
1076 }
1077 $this->mOptions[$oname] = $val;
1078 $this->invalidateCache();
1079 }
1080
1081 function getRights() {
1082 $this->loadFromDatabase();
1083 return $this->mRights;
1084 }
1085
1086 /**
1087 * Get the list of explicit group memberships this user has.
1088 * The implicit * and user groups are not included.
1089 * @return array of strings
1090 */
1091 function getGroups() {
1092 $this->loadFromDatabase();
1093 return $this->mGroups;
1094 }
1095
1096 /**
1097 * Get the list of implicit group memberships this user has.
1098 * This includes all explicit groups, plus 'user' if logged in
1099 * and '*' for all accounts.
1100 * @return array of strings
1101 */
1102 function getEffectiveGroups() {
1103 $base = array( '*' );
1104 if( $this->isLoggedIn() ) {
1105 $base[] = 'user';
1106 }
1107 return array_merge( $base, $this->getGroups() );
1108 }
1109
1110 /**
1111 * Add the user to the given group.
1112 * This takes immediate effect.
1113 * @string $group
1114 */
1115 function addGroup( $group ) {
1116 $dbw =& wfGetDB( DB_MASTER );
1117 $dbw->insert( 'user_groups',
1118 array(
1119 'ug_user' => $this->getID(),
1120 'ug_group' => $group,
1121 ),
1122 'User::addGroup',
1123 array( 'IGNORE' ) );
1124
1125 $this->mGroups = array_merge( $this->mGroups, array( $group ) );
1126 $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
1127
1128 $this->invalidateCache();
1129 $this->saveSettings();
1130 }
1131
1132 /**
1133 * Remove the user from the given group.
1134 * This takes immediate effect.
1135 * @string $group
1136 */
1137 function removeGroup( $group ) {
1138 $dbw =& wfGetDB( DB_MASTER );
1139 $dbw->delete( 'user_groups',
1140 array(
1141 'ug_user' => $this->getID(),
1142 'ug_group' => $group,
1143 ),
1144 'User::removeGroup' );
1145
1146 $this->mGroups = array_diff( $this->mGroups, array( $group ) );
1147 $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
1148
1149 $this->invalidateCache();
1150 $this->saveSettings();
1151 }
1152
1153
1154 /**
1155 * A more legible check for non-anonymousness.
1156 * Returns true if the user is not an anonymous visitor.
1157 *
1158 * @return bool
1159 */
1160 function isLoggedIn() {
1161 return( $this->getID() != 0 );
1162 }
1163
1164 /**
1165 * A more legible check for anonymousness.
1166 * Returns true if the user is an anonymous visitor.
1167 *
1168 * @return bool
1169 */
1170 function isAnon() {
1171 return !$this->isLoggedIn();
1172 }
1173
1174 /**
1175 * Deprecated in 1.6, die in 1.7, to be removed in 1.8
1176 * @deprecated
1177 */
1178 function isSysop() {
1179 wfDebugDieBacktrace( "Call to deprecated (v1.7) User::isSysop() method\n" );
1180 #return $this->isAllowed( 'protect' );
1181 }
1182
1183 /**
1184 * Deprecated in 1.6, die in 1.7, to be removed in 1.8
1185 * @deprecated
1186 */
1187 function isDeveloper() {
1188 wfDebugDieBacktrace( "Call to deprecated (v1.7) User::isDeveloper() method\n" );
1189 #return $this->isAllowed( 'siteadmin' );
1190 }
1191
1192 /**
1193 * Deprecated in 1.6, die in 1.7, to be removed in 1.8
1194 * @deprecated
1195 */
1196 function isBureaucrat() {
1197 wfDebugDieBacktrace( "Call to deprecated (v1.7) User::isBureaucrat() method\n" );
1198 #return $this->isAllowed( 'makesysop' );
1199 }
1200
1201 /**
1202 * Whether the user is a bot
1203 * @todo need to be migrated to the new user level management sytem
1204 */
1205 function isBot() {
1206 $this->loadFromDatabase();
1207 return in_array( 'bot', $this->mRights );
1208 }
1209
1210 /**
1211 * Check if user is allowed to access a feature / make an action
1212 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1213 * @return boolean True: action is allowed, False: action should not be allowed
1214 */
1215 function isAllowed($action='') {
1216 if ( $action === '' )
1217 // In the spirit of DWIM
1218 return true;
1219
1220 $this->loadFromDatabase();
1221 return in_array( $action , $this->mRights );
1222 }
1223
1224 /**
1225 * Load a skin if it doesn't exist or return it
1226 * @todo FIXME : need to check the old failback system [AV]
1227 */
1228 function &getSkin() {
1229 global $IP, $wgRequest;
1230 if ( ! isset( $this->mSkin ) ) {
1231 $fname = 'User::getSkin';
1232 wfProfileIn( $fname );
1233
1234 # get the user skin
1235 $userSkin = $this->getOption( 'skin' );
1236 $userSkin = $wgRequest->getVal('useskin', $userSkin);
1237
1238 $this->mSkin =& Skin::newFromKey( $userSkin );
1239 wfProfileOut( $fname );
1240 }
1241 return $this->mSkin;
1242 }
1243
1244 /**#@+
1245 * @param string $title Article title to look at
1246 */
1247
1248 /**
1249 * Check watched status of an article
1250 * @return bool True if article is watched
1251 */
1252 function isWatched( $title ) {
1253 $wl = WatchedItem::fromUserTitle( $this, $title );
1254 return $wl->isWatched();
1255 }
1256
1257 /**
1258 * Watch an article
1259 */
1260 function addWatch( $title ) {
1261 $wl = WatchedItem::fromUserTitle( $this, $title );
1262 $wl->addWatch();
1263 $this->invalidateCache();
1264 }
1265
1266 /**
1267 * Stop watching an article
1268 */
1269 function removeWatch( $title ) {
1270 $wl = WatchedItem::fromUserTitle( $this, $title );
1271 $wl->removeWatch();
1272 $this->invalidateCache();
1273 }
1274
1275 /**
1276 * Clear the user's notification timestamp for the given title.
1277 * If e-notif e-mails are on, they will receive notification mails on
1278 * the next change of the page if it's watched etc.
1279 */
1280 function clearNotification( &$title ) {
1281 global $wgUser, $wgUseEnotif;
1282
1283
1284 if ($title->getNamespace() == NS_USER_TALK &&
1285 $title->getText() == $this->getName() ) {
1286 if (!wfRunHooks('UserClearNewTalkNotification', array(&$this)))
1287 return;
1288 $this->setNewtalk( false );
1289 }
1290
1291 if( !$wgUseEnotif ) {
1292 return;
1293 }
1294
1295 if( $this->isAnon() ) {
1296 // Nothing else to do...
1297 return;
1298 }
1299
1300 // Only update the timestamp if the page is being watched.
1301 // The query to find out if it is watched is cached both in memcached and per-invocation,
1302 // and when it does have to be executed, it can be on a slave
1303 // If this is the user's newtalk page, we always update the timestamp
1304 if ($title->getNamespace() == NS_USER_TALK &&
1305 $title->getText() == $wgUser->getName())
1306 {
1307 $watched = true;
1308 } elseif ( $this->getID() == $wgUser->getID() ) {
1309 $watched = $title->userIsWatching();
1310 } else {
1311 $watched = true;
1312 }
1313
1314 // If the page is watched by the user (or may be watched), update the timestamp on any
1315 // any matching rows
1316 if ( $watched ) {
1317 $dbw =& wfGetDB( DB_MASTER );
1318 $success = $dbw->update( 'watchlist',
1319 array( /* SET */
1320 'wl_notificationtimestamp' => NULL
1321 ), array( /* WHERE */
1322 'wl_title' => $title->getDBkey(),
1323 'wl_namespace' => $title->getNamespace(),
1324 'wl_user' => $this->getID()
1325 ), 'User::clearLastVisited'
1326 );
1327 }
1328 }
1329
1330 /**#@-*/
1331
1332 /**
1333 * Resets all of the given user's page-change notification timestamps.
1334 * If e-notif e-mails are on, they will receive notification mails on
1335 * the next change of any watched page.
1336 *
1337 * @param int $currentUser user ID number
1338 * @public
1339 */
1340 function clearAllNotifications( $currentUser ) {
1341 global $wgUseEnotif;
1342 if ( !$wgUseEnotif ) {
1343 $this->setNewtalk( false );
1344 return;
1345 }
1346 if( $currentUser != 0 ) {
1347
1348 $dbw =& wfGetDB( DB_MASTER );
1349 $success = $dbw->update( 'watchlist',
1350 array( /* SET */
1351 'wl_notificationtimestamp' => 0
1352 ), array( /* WHERE */
1353 'wl_user' => $currentUser
1354 ), 'UserMailer::clearAll'
1355 );
1356
1357 # we also need to clear here the "you have new message" notification for the own user_talk page
1358 # This is cleared one page view later in Article::viewUpdates();
1359 }
1360 }
1361
1362 /**
1363 * @private
1364 * @return string Encoding options
1365 */
1366 function encodeOptions() {
1367 $a = array();
1368 foreach ( $this->mOptions as $oname => $oval ) {
1369 array_push( $a, $oname.'='.$oval );
1370 }
1371 $s = implode( "\n", $a );
1372 return $s;
1373 }
1374
1375 /**
1376 * @private
1377 */
1378 function decodeOptions( $str ) {
1379 $a = explode( "\n", $str );
1380 foreach ( $a as $s ) {
1381 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1382 $this->mOptions[$m[1]] = $m[2];
1383 }
1384 }
1385 }
1386
1387 function setCookies() {
1388 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
1389 if ( 0 == $this->mId ) return;
1390 $this->loadFromDatabase();
1391 $exp = time() + $wgCookieExpiration;
1392
1393 $_SESSION['wsUserID'] = $this->mId;
1394 setcookie( $wgCookiePrefix.'UserID', $this->mId, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1395
1396 $_SESSION['wsUserName'] = $this->getName();
1397 setcookie( $wgCookiePrefix.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1398
1399 $_SESSION['wsToken'] = $this->mToken;
1400 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1401 setcookie( $wgCookiePrefix.'Token', $this->mToken, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1402 } else {
1403 setcookie( $wgCookiePrefix.'Token', '', time() - 3600 );
1404 }
1405 }
1406
1407 /**
1408 * Logout user
1409 * It will clean the session cookie
1410 */
1411 function logout() {
1412 global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
1413 $this->loadDefaults();
1414 $this->setLoaded( true );
1415
1416 $_SESSION['wsUserID'] = 0;
1417
1418 setcookie( $wgCookiePrefix.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1419 setcookie( $wgCookiePrefix.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1420
1421 # Remember when user logged out, to prevent seeing cached pages
1422 setcookie( $wgCookiePrefix.'LoggedOut', wfTimestampNow(), time() + 86400, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1423 }
1424
1425 /**
1426 * Save object settings into database
1427 */
1428 function saveSettings() {
1429 global $wgMemc, $wgDBname;
1430 $fname = 'User::saveSettings';
1431
1432 if ( wfReadOnly() ) { return; }
1433 if ( 0 == $this->mId ) { return; }
1434
1435 $dbw =& wfGetDB( DB_MASTER );
1436 $dbw->update( 'user',
1437 array( /* SET */
1438 'user_name' => $this->mName,
1439 'user_password' => $this->mPassword,
1440 'user_newpassword' => $this->mNewpassword,
1441 'user_real_name' => $this->mRealName,
1442 'user_email' => $this->mEmail,
1443 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
1444 'user_options' => $this->encodeOptions(),
1445 'user_touched' => $dbw->timestamp($this->mTouched),
1446 'user_token' => $this->mToken
1447 ), array( /* WHERE */
1448 'user_id' => $this->mId
1449 ), $fname
1450 );
1451 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1452 }
1453
1454
1455 /**
1456 * Checks if a user with the given name exists, returns the ID
1457 */
1458 function idForName() {
1459 $fname = 'User::idForName';
1460
1461 $gotid = 0;
1462 $s = trim( $this->getName() );
1463 if ( 0 == strcmp( '', $s ) ) return 0;
1464
1465 $dbr =& wfGetDB( DB_SLAVE );
1466 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1467 if ( $id === false ) {
1468 $id = 0;
1469 }
1470 return $id;
1471 }
1472
1473 /**
1474 * Add user object to the database
1475 */
1476 function addToDatabase() {
1477 $fname = 'User::addToDatabase';
1478 $dbw =& wfGetDB( DB_MASTER );
1479 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1480 $dbw->insert( 'user',
1481 array(
1482 'user_id' => $seqVal,
1483 'user_name' => $this->mName,
1484 'user_password' => $this->mPassword,
1485 'user_newpassword' => $this->mNewpassword,
1486 'user_email' => $this->mEmail,
1487 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
1488 'user_real_name' => $this->mRealName,
1489 'user_options' => $this->encodeOptions(),
1490 'user_token' => $this->mToken,
1491 'user_registration' => $dbw->timestamp( $this->mRegistration ),
1492 ), $fname
1493 );
1494 $this->mId = $dbw->insertId();
1495 }
1496
1497 function spreadBlock() {
1498 # If the (non-anonymous) user is blocked, this function will block any IP address
1499 # that they successfully log on from.
1500 $fname = 'User::spreadBlock';
1501
1502 wfDebug( "User:spreadBlock()\n" );
1503 if ( $this->mId == 0 ) {
1504 return;
1505 }
1506
1507 $userblock = Block::newFromDB( '', $this->mId );
1508 if ( !$userblock->isValid() ) {
1509 return;
1510 }
1511
1512 # Check if this IP address is already blocked
1513 $ipblock = Block::newFromDB( wfGetIP() );
1514 if ( $ipblock->isValid() ) {
1515 # If the user is already blocked. Then check if the autoblock would
1516 # excede the user block. If it would excede, then do nothing, else
1517 # prolong block time
1518 if ($userblock->mExpiry &&
1519 ($userblock->mExpiry < Block::getAutoblockExpiry($ipblock->mTimestamp))) {
1520 return;
1521 }
1522 # Just update the timestamp
1523 $ipblock->updateTimestamp();
1524 return;
1525 }
1526
1527 # Make a new block object with the desired properties
1528 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1529 $ipblock->mAddress = wfGetIP();
1530 $ipblock->mUser = 0;
1531 $ipblock->mBy = $userblock->mBy;
1532 $ipblock->mReason = wfMsg( 'autoblocker', $this->getName(), $userblock->mReason );
1533 $ipblock->mTimestamp = wfTimestampNow();
1534 $ipblock->mAuto = 1;
1535 # If the user is already blocked with an expiry date, we don't
1536 # want to pile on top of that!
1537 if($userblock->mExpiry) {
1538 $ipblock->mExpiry = min ( $userblock->mExpiry, Block::getAutoblockExpiry( $ipblock->mTimestamp ));
1539 } else {
1540 $ipblock->mExpiry = Block::getAutoblockExpiry( $ipblock->mTimestamp );
1541 }
1542
1543 # Insert it
1544 $ipblock->insert();
1545
1546 }
1547
1548 /**
1549 * Generate a string which will be different for any combination of
1550 * user options which would produce different parser output.
1551 * This will be used as part of the hash key for the parser cache,
1552 * so users will the same options can share the same cached data
1553 * safely.
1554 *
1555 * Extensions which require it should install 'PageRenderingHash' hook,
1556 * which will give them a chance to modify this key based on their own
1557 * settings.
1558 *
1559 * @return string
1560 */
1561 function getPageRenderingHash() {
1562 global $wgContLang;
1563 if( $this->mHash ){
1564 return $this->mHash;
1565 }
1566
1567 // stubthreshold is only included below for completeness,
1568 // it will always be 0 when this function is called by parsercache.
1569
1570 $confstr = $this->getOption( 'math' );
1571 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1572 $confstr .= '!' . $this->getOption( 'date' );
1573 $confstr .= '!' . ($this->getOption( 'numberheadings' ) ? '1' : '');
1574 $confstr .= '!' . $this->getOption( 'language' );
1575 $confstr .= '!' . $this->getOption( 'thumbsize' );
1576 // add in language specific options, if any
1577 $extra = $wgContLang->getExtraHashOptions();
1578 $confstr .= $extra;
1579
1580 // Give a chance for extensions to modify the hash, if they have
1581 // extra options or other effects on the parser cache.
1582 wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
1583
1584 $this->mHash = $confstr;
1585 return $confstr;
1586 }
1587
1588 function isAllowedToCreateAccount() {
1589 return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
1590 }
1591
1592 /**
1593 * Set mDataLoaded, return previous value
1594 * Use this to prevent DB access in command-line scripts or similar situations
1595 */
1596 function setLoaded( $loaded ) {
1597 return wfSetVar( $this->mDataLoaded, $loaded );
1598 }
1599
1600 /**
1601 * Get this user's personal page title.
1602 *
1603 * @return Title
1604 * @public
1605 */
1606 function getUserPage() {
1607 return Title::makeTitle( NS_USER, $this->getName() );
1608 }
1609
1610 /**
1611 * Get this user's talk page title.
1612 *
1613 * @return Title
1614 * @public
1615 */
1616 function getTalkPage() {
1617 $title = $this->getUserPage();
1618 return $title->getTalkPage();
1619 }
1620
1621 /**
1622 * @static
1623 */
1624 function getMaxID() {
1625 static $res; // cache
1626
1627 if ( isset( $res ) )
1628 return $res;
1629 else {
1630 $dbr =& wfGetDB( DB_SLAVE );
1631 return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1632 }
1633 }
1634
1635 /**
1636 * Determine whether the user is a newbie. Newbies are either
1637 * anonymous IPs, or the most recently created accounts.
1638 * @return bool True if it is a newbie.
1639 */
1640 function isNewbie() {
1641 return !$this->isAllowed( 'autoconfirmed' );
1642 }
1643
1644 /**
1645 * Check to see if the given clear-text password is one of the accepted passwords
1646 * @param string $password User password.
1647 * @return bool True if the given password is correct otherwise False.
1648 */
1649 function checkPassword( $password ) {
1650 global $wgAuth, $wgMinimalPasswordLength;
1651 $this->loadFromDatabase();
1652
1653 // Even though we stop people from creating passwords that
1654 // are shorter than this, doesn't mean people wont be able
1655 // to. Certain authentication plugins do NOT want to save
1656 // domain passwords in a mysql database, so we should
1657 // check this (incase $wgAuth->strict() is false).
1658 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1659 return false;
1660 }
1661
1662 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1663 return true;
1664 } elseif( $wgAuth->strict() ) {
1665 /* Auth plugin doesn't allow local authentication */
1666 return false;
1667 }
1668 $ep = $this->encryptPassword( $password );
1669 if ( 0 == strcmp( $ep, $this->mPassword ) ) {
1670 return true;
1671 } elseif ( ($this->mNewpassword != '') && (0 == strcmp( $ep, $this->mNewpassword )) ) {
1672 return true;
1673 } elseif ( function_exists( 'iconv' ) ) {
1674 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1675 # Check for this with iconv
1676 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1677 if ( 0 == strcmp( $cp1252hash, $this->mPassword ) ) {
1678 return true;
1679 }
1680 }
1681 return false;
1682 }
1683
1684 /**
1685 * Initialize (if necessary) and return a session token value
1686 * which can be used in edit forms to show that the user's
1687 * login credentials aren't being hijacked with a foreign form
1688 * submission.
1689 *
1690 * @param mixed $salt - Optional function-specific data for hash.
1691 * Use a string or an array of strings.
1692 * @return string
1693 * @public
1694 */
1695 function editToken( $salt = '' ) {
1696 if( !isset( $_SESSION['wsEditToken'] ) ) {
1697 $token = $this->generateToken();
1698 $_SESSION['wsEditToken'] = $token;
1699 } else {
1700 $token = $_SESSION['wsEditToken'];
1701 }
1702 if( is_array( $salt ) ) {
1703 $salt = implode( '|', $salt );
1704 }
1705 return md5( $token . $salt );
1706 }
1707
1708 /**
1709 * Generate a hex-y looking random token for various uses.
1710 * Could be made more cryptographically sure if someone cares.
1711 * @return string
1712 */
1713 function generateToken( $salt = '' ) {
1714 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1715 return md5( $token . $salt );
1716 }
1717
1718 /**
1719 * Check given value against the token value stored in the session.
1720 * A match should confirm that the form was submitted from the
1721 * user's own login session, not a form submission from a third-party
1722 * site.
1723 *
1724 * @param string $val - the input value to compare
1725 * @param string $salt - Optional function-specific data for hash
1726 * @return bool
1727 * @public
1728 */
1729 function matchEditToken( $val, $salt = '' ) {
1730 global $wgMemc;
1731 $sessionToken = $this->editToken( $salt );
1732 if ( $val != $sessionToken ) {
1733 wfDebug( "User::matchEditToken: broken session data\n" );
1734 }
1735 return $val == $sessionToken;
1736 }
1737
1738 /**
1739 * Generate a new e-mail confirmation token and send a confirmation
1740 * mail to the user's given address.
1741 *
1742 * @return mixed True on success, a WikiError object on failure.
1743 */
1744 function sendConfirmationMail() {
1745 global $wgContLang;
1746 $url = $this->confirmationTokenUrl( $expiration );
1747 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1748 wfMsg( 'confirmemail_body',
1749 wfGetIP(),
1750 $this->getName(),
1751 $url,
1752 $wgContLang->timeanddate( $expiration, false ) ) );
1753 }
1754
1755 /**
1756 * Send an e-mail to this user's account. Does not check for
1757 * confirmed status or validity.
1758 *
1759 * @param string $subject
1760 * @param string $body
1761 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1762 * @return mixed True on success, a WikiError object on failure.
1763 */
1764 function sendMail( $subject, $body, $from = null ) {
1765 if( is_null( $from ) ) {
1766 global $wgPasswordSender;
1767 $from = $wgPasswordSender;
1768 }
1769
1770 require_once( 'UserMailer.php' );
1771 $to = new MailAddress( $this );
1772 $sender = new MailAddress( $from );
1773 $error = userMailer( $to, $sender, $subject, $body );
1774
1775 if( $error == '' ) {
1776 return true;
1777 } else {
1778 return new WikiError( $error );
1779 }
1780 }
1781
1782 /**
1783 * Generate, store, and return a new e-mail confirmation code.
1784 * A hash (unsalted since it's used as a key) is stored.
1785 * @param &$expiration mixed output: accepts the expiration time
1786 * @return string
1787 * @private
1788 */
1789 function confirmationToken( &$expiration ) {
1790 $fname = 'User::confirmationToken';
1791
1792 $now = time();
1793 $expires = $now + 7 * 24 * 60 * 60;
1794 $expiration = wfTimestamp( TS_MW, $expires );
1795
1796 $token = $this->generateToken( $this->mId . $this->mEmail . $expires );
1797 $hash = md5( $token );
1798
1799 $dbw =& wfGetDB( DB_MASTER );
1800 $dbw->update( 'user',
1801 array( 'user_email_token' => $hash,
1802 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1803 array( 'user_id' => $this->mId ),
1804 $fname );
1805
1806 return $token;
1807 }
1808
1809 /**
1810 * Generate and store a new e-mail confirmation token, and return
1811 * the URL the user can use to confirm.
1812 * @param &$expiration mixed output: accepts the expiration time
1813 * @return string
1814 * @private
1815 */
1816 function confirmationTokenUrl( &$expiration ) {
1817 $token = $this->confirmationToken( $expiration );
1818 $title = Title::makeTitle( NS_SPECIAL, 'Confirmemail/' . $token );
1819 return $title->getFullUrl();
1820 }
1821
1822 /**
1823 * Mark the e-mail address confirmed and save.
1824 */
1825 function confirmEmail() {
1826 $this->loadFromDatabase();
1827 $this->mEmailAuthenticated = wfTimestampNow();
1828 $this->saveSettings();
1829 return true;
1830 }
1831
1832 /**
1833 * Is this user allowed to send e-mails within limits of current
1834 * site configuration?
1835 * @return bool
1836 */
1837 function canSendEmail() {
1838 return $this->isEmailConfirmed();
1839 }
1840
1841 /**
1842 * Is this user allowed to receive e-mails within limits of current
1843 * site configuration?
1844 * @return bool
1845 */
1846 function canReceiveEmail() {
1847 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1848 }
1849
1850 /**
1851 * Is this user's e-mail address valid-looking and confirmed within
1852 * limits of the current site configuration?
1853 *
1854 * If $wgEmailAuthentication is on, this may require the user to have
1855 * confirmed their address by returning a code or using a password
1856 * sent to the address from the wiki.
1857 *
1858 * @return bool
1859 */
1860 function isEmailConfirmed() {
1861 global $wgEmailAuthentication;
1862 $this->loadFromDatabase();
1863 $confirmed = true;
1864 if( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
1865 if( $this->isAnon() )
1866 return false;
1867 if( !$this->isValidEmailAddr( $this->mEmail ) )
1868 return false;
1869 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1870 return false;
1871 return true;
1872 } else {
1873 return $confirmed;
1874 }
1875 }
1876
1877 /**
1878 * @param array $groups list of groups
1879 * @return array list of permission key names for given groups combined
1880 * @static
1881 */
1882 function getGroupPermissions( $groups ) {
1883 global $wgGroupPermissions;
1884 $rights = array();
1885 foreach( $groups as $group ) {
1886 if( isset( $wgGroupPermissions[$group] ) ) {
1887 $rights = array_merge( $rights,
1888 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1889 }
1890 }
1891 return $rights;
1892 }
1893
1894 /**
1895 * @param string $group key name
1896 * @return string localized descriptive name for group, if provided
1897 * @static
1898 */
1899 function getGroupName( $group ) {
1900 $key = "group-$group";
1901 $name = wfMsg( $key );
1902 if( $name == '' || $name == "&lt;$key&gt;" ) {
1903 return $group;
1904 } else {
1905 return $name;
1906 }
1907 }
1908
1909 /**
1910 * @param string $group key name
1911 * @return string localized descriptive name for member of a group, if provided
1912 * @static
1913 */
1914 function getGroupMember( $group ) {
1915 $key = "group-$group-member";
1916 $name = wfMsg( $key );
1917 if( $name == '' || $name == "&lt;$key&gt;" ) {
1918 return $group;
1919 } else {
1920 return $name;
1921 }
1922 }
1923
1924
1925 /**
1926 * Return the set of defined explicit groups.
1927 * The * and 'user' groups are not included.
1928 * @return array
1929 * @static
1930 */
1931 function getAllGroups() {
1932 global $wgGroupPermissions;
1933 return array_diff(
1934 array_keys( $wgGroupPermissions ),
1935 array( '*', 'user', 'autoconfirmed' ) );
1936 }
1937
1938 /**
1939 * Get the title of a page describing a particular group
1940 *
1941 * @param $group Name of the group
1942 * @return mixed
1943 */
1944 function getGroupPage( $group ) {
1945 $page = wfMsgForContent( 'grouppage-' . $group );
1946 if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) {
1947 $title = Title::newFromText( $page );
1948 if( is_object( $title ) )
1949 return $title;
1950 }
1951 return false;
1952 }
1953
1954
1955 }
1956
1957 ?>