last round of PHP5 stuff for now.
[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 static 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 public static 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 * @static
645 */
646 static function SetupSession() {
647 global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain;
648 if( $wgSessionsInMemcached ) {
649 require_once( 'MemcachedSessions.php' );
650 } elseif( 'files' != ini_get( 'session.save_handler' ) ) {
651 # If it's left on 'user' or another setting from another
652 # application, it will end up failing. Try to recover.
653 ini_set ( 'session.save_handler', 'files' );
654 }
655 session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain );
656 session_cache_limiter( 'private, must-revalidate' );
657 @session_start();
658 }
659
660 /**
661 * Create a new user object using data from session
662 * @static
663 */
664 static function loadFromSession() {
665 global $wgMemc, $wgDBname, $wgCookiePrefix;
666
667 if ( isset( $_SESSION['wsUserID'] ) ) {
668 if ( 0 != $_SESSION['wsUserID'] ) {
669 $sId = $_SESSION['wsUserID'];
670 } else {
671 return new User();
672 }
673 } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
674 $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
675 $_SESSION['wsUserID'] = $sId;
676 } else {
677 return new User();
678 }
679 if ( isset( $_SESSION['wsUserName'] ) ) {
680 $sName = $_SESSION['wsUserName'];
681 } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) {
682 $sName = $_COOKIE["{$wgCookiePrefix}UserName"];
683 $_SESSION['wsUserName'] = $sName;
684 } else {
685 return new User();
686 }
687
688 $passwordCorrect = FALSE;
689 $user = $wgMemc->get( $key = "$wgDBname:user:id:$sId" );
690 if( !is_object( $user ) || $user->mVersion < MW_USER_VERSION ) {
691 # Expire old serialized objects; they may be corrupt.
692 $user = false;
693 }
694 if($makenew = !$user) {
695 wfDebug( "User::loadFromSession() unable to load from memcached\n" );
696 $user = new User();
697 $user->mId = $sId;
698 $user->loadFromDatabase();
699 } else {
700 wfDebug( "User::loadFromSession() got from cache!\n" );
701 }
702
703 if ( isset( $_SESSION['wsToken'] ) ) {
704 $passwordCorrect = $_SESSION['wsToken'] == $user->mToken;
705 } else if ( isset( $_COOKIE["{$wgCookiePrefix}Token"] ) ) {
706 $passwordCorrect = $user->mToken == $_COOKIE["{$wgCookiePrefix}Token"];
707 } else {
708 return new User(); # Can't log in from session
709 }
710
711 if ( ( $sName == $user->mName ) && $passwordCorrect ) {
712 if($makenew) {
713 if($wgMemc->set( $key, $user ))
714 wfDebug( "User::loadFromSession() successfully saved user\n" );
715 else
716 wfDebug( "User::loadFromSession() unable to save to memcached\n" );
717 }
718 return $user;
719 }
720 return new User(); # Can't log in from session
721 }
722
723 /**
724 * Load a user from the database
725 */
726 function loadFromDatabase() {
727 $fname = "User::loadFromDatabase";
728
729 # Counter-intuitive, breaks various things, use User::setLoaded() if you want to suppress
730 # loading in a command line script, don't assume all command line scripts need it like this
731 #if ( $this->mDataLoaded || $wgCommandLineMode ) {
732 if ( $this->mDataLoaded ) {
733 return;
734 }
735
736 # Paranoia
737 $this->mId = intval( $this->mId );
738
739 /** Anonymous user */
740 if( !$this->mId ) {
741 /** Get rights */
742 $this->mRights = $this->getGroupPermissions( array( '*' ) );
743 $this->mDataLoaded = true;
744 return;
745 } # the following stuff is for non-anonymous users only
746
747 $dbr =& wfGetDB( DB_SLAVE );
748 $s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
749 'user_email_authenticated',
750 'user_real_name','user_options','user_touched', 'user_token', 'user_registration' ),
751 array( 'user_id' => $this->mId ), $fname );
752
753 if ( $s !== false ) {
754 $this->mName = $s->user_name;
755 $this->mEmail = $s->user_email;
756 $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $s->user_email_authenticated );
757 $this->mRealName = $s->user_real_name;
758 $this->mPassword = $s->user_password;
759 $this->mNewpassword = $s->user_newpassword;
760 $this->decodeOptions( $s->user_options );
761 $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
762 $this->mToken = $s->user_token;
763 $this->mRegistration = wfTimestampOrNull( TS_MW, $s->user_registration );
764
765 $res = $dbr->select( 'user_groups',
766 array( 'ug_group' ),
767 array( 'ug_user' => $this->mId ),
768 $fname );
769 $this->mGroups = array();
770 while( $row = $dbr->fetchObject( $res ) ) {
771 $this->mGroups[] = $row->ug_group;
772 }
773 $implicitGroups = array( '*', 'user' );
774
775 global $wgAutoConfirmAge;
776 $accountAge = time() - wfTimestampOrNull( TS_UNIX, $this->mRegistration );
777 if( $accountAge >= $wgAutoConfirmAge ) {
778 $implicitGroups[] = 'autoconfirmed';
779 }
780
781 $effectiveGroups = array_merge( $implicitGroups, $this->mGroups );
782 $this->mRights = $this->getGroupPermissions( $effectiveGroups );
783 }
784
785 $this->mDataLoaded = true;
786 }
787
788 function getID() { return $this->mId; }
789 function setID( $v ) {
790 $this->mId = $v;
791 $this->mDataLoaded = false;
792 }
793
794 function getName() {
795 $this->loadFromDatabase();
796 if ( $this->mName === false ) {
797 $this->mName = wfGetIP();
798 }
799 return $this->mName;
800 }
801
802 function setName( $str ) {
803 $this->loadFromDatabase();
804 $this->mName = $str;
805 }
806
807
808 /**
809 * Return the title dbkey form of the name, for eg user pages.
810 * @return string
811 * @public
812 */
813 function getTitleKey() {
814 return str_replace( ' ', '_', $this->getName() );
815 }
816
817 function getNewtalk() {
818 $this->loadFromDatabase();
819
820 # Load the newtalk status if it is unloaded (mNewtalk=-1)
821 if( $this->mNewtalk === -1 ) {
822 $this->mNewtalk = false; # reset talk page status
823
824 # Check memcached separately for anons, who have no
825 # entire User object stored in there.
826 if( !$this->mId ) {
827 global $wgDBname, $wgMemc;
828 $key = "$wgDBname:newtalk:ip:" . $this->getName();
829 $newtalk = $wgMemc->get( $key );
830 if( is_integer( $newtalk ) ) {
831 $this->mNewtalk = (bool)$newtalk;
832 } else {
833 $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
834 $wgMemc->set( $key, $this->mNewtalk, time() ); // + 1800 );
835 }
836 } else {
837 $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
838 }
839 }
840
841 return (bool)$this->mNewtalk;
842 }
843
844 /**
845 * Return the talk page(s) this user has new messages on.
846 */
847 function getNewMessageLinks() {
848 global $wgDBname;
849 $talks = array();
850 if (!wfRunHooks('UserRetrieveNewTalks', array(&$this, &$talks)))
851 return $talks;
852
853 if (!$this->getNewtalk())
854 return array();
855 $up = $this->getUserPage();
856 $utp = $up->getTalkPage();
857 return array(array("wiki" => $wgDBname, "link" => $utp->getLocalURL()));
858 }
859
860
861 /**
862 * Perform a user_newtalk check on current slaves; if the memcached data
863 * is funky we don't want newtalk state to get stuck on save, as that's
864 * damn annoying.
865 *
866 * @param string $field
867 * @param mixed $id
868 * @return bool
869 * @private
870 */
871 function checkNewtalk( $field, $id ) {
872 $fname = 'User::checkNewtalk';
873 $dbr =& wfGetDB( DB_SLAVE );
874 $ok = $dbr->selectField( 'user_newtalk', $field,
875 array( $field => $id ), $fname );
876 return $ok !== false;
877 }
878
879 /**
880 * Add or update the
881 * @param string $field
882 * @param mixed $id
883 * @private
884 */
885 function updateNewtalk( $field, $id ) {
886 $fname = 'User::updateNewtalk';
887 if( $this->checkNewtalk( $field, $id ) ) {
888 wfDebug( "$fname already set ($field, $id), ignoring\n" );
889 return false;
890 }
891 $dbw =& wfGetDB( DB_MASTER );
892 $dbw->insert( 'user_newtalk',
893 array( $field => $id ),
894 $fname,
895 'IGNORE' );
896 wfDebug( "$fname: set on ($field, $id)\n" );
897 return true;
898 }
899
900 /**
901 * Clear the new messages flag for the given user
902 * @param string $field
903 * @param mixed $id
904 * @private
905 */
906 function deleteNewtalk( $field, $id ) {
907 $fname = 'User::deleteNewtalk';
908 if( !$this->checkNewtalk( $field, $id ) ) {
909 wfDebug( "$fname: already gone ($field, $id), ignoring\n" );
910 return false;
911 }
912 $dbw =& wfGetDB( DB_MASTER );
913 $dbw->delete( 'user_newtalk',
914 array( $field => $id ),
915 $fname );
916 wfDebug( "$fname: killed on ($field, $id)\n" );
917 return true;
918 }
919
920 /**
921 * Update the 'You have new messages!' status.
922 * @param bool $val
923 */
924 function setNewtalk( $val ) {
925 if( wfReadOnly() ) {
926 return;
927 }
928
929 $this->loadFromDatabase();
930 $this->mNewtalk = $val;
931
932 $fname = 'User::setNewtalk';
933
934 if( $this->isAnon() ) {
935 $field = 'user_ip';
936 $id = $this->getName();
937 } else {
938 $field = 'user_id';
939 $id = $this->getId();
940 }
941
942 if( $val ) {
943 $changed = $this->updateNewtalk( $field, $id );
944 } else {
945 $changed = $this->deleteNewtalk( $field, $id );
946 }
947
948 if( $changed ) {
949 if( $this->isAnon() ) {
950 // Anons have a separate memcached space, since
951 // user records aren't kept for them.
952 global $wgDBname, $wgMemc;
953 $key = "$wgDBname:newtalk:ip:$val";
954 $wgMemc->set( $key, $val ? 1 : 0 );
955 } else {
956 if( $val ) {
957 // Make sure the user page is watched, so a notification
958 // will be sent out if enabled.
959 $this->addWatch( $this->getTalkPage() );
960 }
961 }
962 $this->invalidateCache();
963 $this->saveSettings();
964 }
965 }
966
967 function invalidateCache() {
968 global $wgClockSkewFudge;
969 $this->loadFromDatabase();
970 $this->mTouched = wfTimestamp(TS_MW, time() + $wgClockSkewFudge );
971 # Don't forget to save the options after this or
972 # it won't take effect!
973 }
974
975 function validateCache( $timestamp ) {
976 $this->loadFromDatabase();
977 return ($timestamp >= $this->mTouched);
978 }
979
980 /**
981 * Encrypt a password.
982 * It can eventuall salt a password @see User::addSalt()
983 * @param string $p clear Password.
984 * @return string Encrypted password.
985 */
986 function encryptPassword( $p ) {
987 return wfEncryptPassword( $this->mId, $p );
988 }
989
990 # Set the password and reset the random token
991 function setPassword( $str ) {
992 $this->loadFromDatabase();
993 $this->setToken();
994 $this->mPassword = $this->encryptPassword( $str );
995 $this->mNewpassword = '';
996 }
997
998 # Set the random token (used for persistent authentication)
999 function setToken( $token = false ) {
1000 global $wgSecretKey, $wgProxyKey, $wgDBname;
1001 if ( !$token ) {
1002 if ( $wgSecretKey ) {
1003 $key = $wgSecretKey;
1004 } elseif ( $wgProxyKey ) {
1005 $key = $wgProxyKey;
1006 } else {
1007 $key = microtime();
1008 }
1009 $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . $wgDBname . $this->mId );
1010 } else {
1011 $this->mToken = $token;
1012 }
1013 }
1014
1015
1016 function setCookiePassword( $str ) {
1017 $this->loadFromDatabase();
1018 $this->mCookiePassword = md5( $str );
1019 }
1020
1021 function setNewpassword( $str ) {
1022 $this->loadFromDatabase();
1023 $this->mNewpassword = $this->encryptPassword( $str );
1024 }
1025
1026 function getEmail() {
1027 $this->loadFromDatabase();
1028 return $this->mEmail;
1029 }
1030
1031 function getEmailAuthenticationTimestamp() {
1032 $this->loadFromDatabase();
1033 return $this->mEmailAuthenticated;
1034 }
1035
1036 function setEmail( $str ) {
1037 $this->loadFromDatabase();
1038 $this->mEmail = $str;
1039 }
1040
1041 function getRealName() {
1042 $this->loadFromDatabase();
1043 return $this->mRealName;
1044 }
1045
1046 function setRealName( $str ) {
1047 $this->loadFromDatabase();
1048 $this->mRealName = $str;
1049 }
1050
1051 /**
1052 * @param string $oname The option to check
1053 * @return string
1054 */
1055 function getOption( $oname ) {
1056 $this->loadFromDatabase();
1057 if ( array_key_exists( $oname, $this->mOptions ) ) {
1058 return trim( $this->mOptions[$oname] );
1059 } else {
1060 return '';
1061 }
1062 }
1063
1064 /**
1065 * @param string $oname The option to check
1066 * @return bool False if the option is not selected, true if it is
1067 */
1068 function getBoolOption( $oname ) {
1069 return (bool)$this->getOption( $oname );
1070 }
1071
1072 function setOption( $oname, $val ) {
1073 $this->loadFromDatabase();
1074 if ( $oname == 'skin' ) {
1075 # Clear cached skin, so the new one displays immediately in Special:Preferences
1076 unset( $this->mSkin );
1077 }
1078 $this->mOptions[$oname] = $val;
1079 $this->invalidateCache();
1080 }
1081
1082 function getRights() {
1083 $this->loadFromDatabase();
1084 return $this->mRights;
1085 }
1086
1087 /**
1088 * Get the list of explicit group memberships this user has.
1089 * The implicit * and user groups are not included.
1090 * @return array of strings
1091 */
1092 function getGroups() {
1093 $this->loadFromDatabase();
1094 return $this->mGroups;
1095 }
1096
1097 /**
1098 * Get the list of implicit group memberships this user has.
1099 * This includes all explicit groups, plus 'user' if logged in
1100 * and '*' for all accounts.
1101 * @return array of strings
1102 */
1103 function getEffectiveGroups() {
1104 $base = array( '*' );
1105 if( $this->isLoggedIn() ) {
1106 $base[] = 'user';
1107 }
1108 return array_merge( $base, $this->getGroups() );
1109 }
1110
1111 /**
1112 * Add the user to the given group.
1113 * This takes immediate effect.
1114 * @string $group
1115 */
1116 function addGroup( $group ) {
1117 $dbw =& wfGetDB( DB_MASTER );
1118 $dbw->insert( 'user_groups',
1119 array(
1120 'ug_user' => $this->getID(),
1121 'ug_group' => $group,
1122 ),
1123 'User::addGroup',
1124 array( 'IGNORE' ) );
1125
1126 $this->mGroups = array_merge( $this->mGroups, array( $group ) );
1127 $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
1128
1129 $this->invalidateCache();
1130 $this->saveSettings();
1131 }
1132
1133 /**
1134 * Remove the user from the given group.
1135 * This takes immediate effect.
1136 * @string $group
1137 */
1138 function removeGroup( $group ) {
1139 $dbw =& wfGetDB( DB_MASTER );
1140 $dbw->delete( 'user_groups',
1141 array(
1142 'ug_user' => $this->getID(),
1143 'ug_group' => $group,
1144 ),
1145 'User::removeGroup' );
1146
1147 $this->mGroups = array_diff( $this->mGroups, array( $group ) );
1148 $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups() );
1149
1150 $this->invalidateCache();
1151 $this->saveSettings();
1152 }
1153
1154
1155 /**
1156 * A more legible check for non-anonymousness.
1157 * Returns true if the user is not an anonymous visitor.
1158 *
1159 * @return bool
1160 */
1161 function isLoggedIn() {
1162 return( $this->getID() != 0 );
1163 }
1164
1165 /**
1166 * A more legible check for anonymousness.
1167 * Returns true if the user is an anonymous visitor.
1168 *
1169 * @return bool
1170 */
1171 function isAnon() {
1172 return !$this->isLoggedIn();
1173 }
1174
1175 /**
1176 * Deprecated in 1.6, die in 1.7, to be removed in 1.8
1177 * @deprecated
1178 */
1179 function isSysop() {
1180 wfDebugDieBacktrace( "Call to deprecated (v1.7) User::isSysop() method\n" );
1181 #return $this->isAllowed( 'protect' );
1182 }
1183
1184 /**
1185 * Deprecated in 1.6, die in 1.7, to be removed in 1.8
1186 * @deprecated
1187 */
1188 function isDeveloper() {
1189 wfDebugDieBacktrace( "Call to deprecated (v1.7) User::isDeveloper() method\n" );
1190 #return $this->isAllowed( 'siteadmin' );
1191 }
1192
1193 /**
1194 * Deprecated in 1.6, die in 1.7, to be removed in 1.8
1195 * @deprecated
1196 */
1197 function isBureaucrat() {
1198 wfDebugDieBacktrace( "Call to deprecated (v1.7) User::isBureaucrat() method\n" );
1199 #return $this->isAllowed( 'makesysop' );
1200 }
1201
1202 /**
1203 * Whether the user is a bot
1204 * @todo need to be migrated to the new user level management sytem
1205 */
1206 function isBot() {
1207 $this->loadFromDatabase();
1208 return in_array( 'bot', $this->mRights );
1209 }
1210
1211 /**
1212 * Check if user is allowed to access a feature / make an action
1213 * @param string $action Action to be checked (see $wgAvailableRights in Defines.php for possible actions).
1214 * @return boolean True: action is allowed, False: action should not be allowed
1215 */
1216 function isAllowed($action='') {
1217 if ( $action === '' )
1218 // In the spirit of DWIM
1219 return true;
1220
1221 $this->loadFromDatabase();
1222 return in_array( $action , $this->mRights );
1223 }
1224
1225 /**
1226 * Load a skin if it doesn't exist or return it
1227 * @todo FIXME : need to check the old failback system [AV]
1228 */
1229 function &getSkin() {
1230 global $IP, $wgRequest;
1231 if ( ! isset( $this->mSkin ) ) {
1232 $fname = 'User::getSkin';
1233 wfProfileIn( $fname );
1234
1235 # get the user skin
1236 $userSkin = $this->getOption( 'skin' );
1237 $userSkin = $wgRequest->getVal('useskin', $userSkin);
1238
1239 $this->mSkin =& Skin::newFromKey( $userSkin );
1240 wfProfileOut( $fname );
1241 }
1242 return $this->mSkin;
1243 }
1244
1245 /**#@+
1246 * @param string $title Article title to look at
1247 */
1248
1249 /**
1250 * Check watched status of an article
1251 * @return bool True if article is watched
1252 */
1253 function isWatched( $title ) {
1254 $wl = WatchedItem::fromUserTitle( $this, $title );
1255 return $wl->isWatched();
1256 }
1257
1258 /**
1259 * Watch an article
1260 */
1261 function addWatch( $title ) {
1262 $wl = WatchedItem::fromUserTitle( $this, $title );
1263 $wl->addWatch();
1264 $this->invalidateCache();
1265 }
1266
1267 /**
1268 * Stop watching an article
1269 */
1270 function removeWatch( $title ) {
1271 $wl = WatchedItem::fromUserTitle( $this, $title );
1272 $wl->removeWatch();
1273 $this->invalidateCache();
1274 }
1275
1276 /**
1277 * Clear the user's notification timestamp for the given title.
1278 * If e-notif e-mails are on, they will receive notification mails on
1279 * the next change of the page if it's watched etc.
1280 */
1281 function clearNotification( &$title ) {
1282 global $wgUser, $wgUseEnotif;
1283
1284
1285 if ($title->getNamespace() == NS_USER_TALK &&
1286 $title->getText() == $this->getName() ) {
1287 if (!wfRunHooks('UserClearNewTalkNotification', array(&$this)))
1288 return;
1289 $this->setNewtalk( false );
1290 }
1291
1292 if( !$wgUseEnotif ) {
1293 return;
1294 }
1295
1296 if( $this->isAnon() ) {
1297 // Nothing else to do...
1298 return;
1299 }
1300
1301 // Only update the timestamp if the page is being watched.
1302 // The query to find out if it is watched is cached both in memcached and per-invocation,
1303 // and when it does have to be executed, it can be on a slave
1304 // If this is the user's newtalk page, we always update the timestamp
1305 if ($title->getNamespace() == NS_USER_TALK &&
1306 $title->getText() == $wgUser->getName())
1307 {
1308 $watched = true;
1309 } elseif ( $this->getID() == $wgUser->getID() ) {
1310 $watched = $title->userIsWatching();
1311 } else {
1312 $watched = true;
1313 }
1314
1315 // If the page is watched by the user (or may be watched), update the timestamp on any
1316 // any matching rows
1317 if ( $watched ) {
1318 $dbw =& wfGetDB( DB_MASTER );
1319 $success = $dbw->update( 'watchlist',
1320 array( /* SET */
1321 'wl_notificationtimestamp' => NULL
1322 ), array( /* WHERE */
1323 'wl_title' => $title->getDBkey(),
1324 'wl_namespace' => $title->getNamespace(),
1325 'wl_user' => $this->getID()
1326 ), 'User::clearLastVisited'
1327 );
1328 }
1329 }
1330
1331 /**#@-*/
1332
1333 /**
1334 * Resets all of the given user's page-change notification timestamps.
1335 * If e-notif e-mails are on, they will receive notification mails on
1336 * the next change of any watched page.
1337 *
1338 * @param int $currentUser user ID number
1339 * @public
1340 */
1341 function clearAllNotifications( $currentUser ) {
1342 global $wgUseEnotif;
1343 if ( !$wgUseEnotif ) {
1344 $this->setNewtalk( false );
1345 return;
1346 }
1347 if( $currentUser != 0 ) {
1348
1349 $dbw =& wfGetDB( DB_MASTER );
1350 $success = $dbw->update( 'watchlist',
1351 array( /* SET */
1352 'wl_notificationtimestamp' => 0
1353 ), array( /* WHERE */
1354 'wl_user' => $currentUser
1355 ), 'UserMailer::clearAll'
1356 );
1357
1358 # we also need to clear here the "you have new message" notification for the own user_talk page
1359 # This is cleared one page view later in Article::viewUpdates();
1360 }
1361 }
1362
1363 /**
1364 * @private
1365 * @return string Encoding options
1366 */
1367 function encodeOptions() {
1368 $a = array();
1369 foreach ( $this->mOptions as $oname => $oval ) {
1370 array_push( $a, $oname.'='.$oval );
1371 }
1372 $s = implode( "\n", $a );
1373 return $s;
1374 }
1375
1376 /**
1377 * @private
1378 */
1379 function decodeOptions( $str ) {
1380 $a = explode( "\n", $str );
1381 foreach ( $a as $s ) {
1382 if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
1383 $this->mOptions[$m[1]] = $m[2];
1384 }
1385 }
1386 }
1387
1388 function setCookies() {
1389 global $wgCookieExpiration, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
1390 if ( 0 == $this->mId ) return;
1391 $this->loadFromDatabase();
1392 $exp = time() + $wgCookieExpiration;
1393
1394 $_SESSION['wsUserID'] = $this->mId;
1395 setcookie( $wgCookiePrefix.'UserID', $this->mId, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1396
1397 $_SESSION['wsUserName'] = $this->getName();
1398 setcookie( $wgCookiePrefix.'UserName', $this->getName(), $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1399
1400 $_SESSION['wsToken'] = $this->mToken;
1401 if ( 1 == $this->getOption( 'rememberpassword' ) ) {
1402 setcookie( $wgCookiePrefix.'Token', $this->mToken, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1403 } else {
1404 setcookie( $wgCookiePrefix.'Token', '', time() - 3600 );
1405 }
1406 }
1407
1408 /**
1409 * Logout user
1410 * It will clean the session cookie
1411 */
1412 function logout() {
1413 global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
1414 $this->loadDefaults();
1415 $this->setLoaded( true );
1416
1417 $_SESSION['wsUserID'] = 0;
1418
1419 setcookie( $wgCookiePrefix.'UserID', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1420 setcookie( $wgCookiePrefix.'Token', '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1421
1422 # Remember when user logged out, to prevent seeing cached pages
1423 setcookie( $wgCookiePrefix.'LoggedOut', wfTimestampNow(), time() + 86400, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
1424 }
1425
1426 /**
1427 * Save object settings into database
1428 */
1429 function saveSettings() {
1430 global $wgMemc, $wgDBname;
1431 $fname = 'User::saveSettings';
1432
1433 if ( wfReadOnly() ) { return; }
1434 if ( 0 == $this->mId ) { return; }
1435
1436 $dbw =& wfGetDB( DB_MASTER );
1437 $dbw->update( 'user',
1438 array( /* SET */
1439 'user_name' => $this->mName,
1440 'user_password' => $this->mPassword,
1441 'user_newpassword' => $this->mNewpassword,
1442 'user_real_name' => $this->mRealName,
1443 'user_email' => $this->mEmail,
1444 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
1445 'user_options' => $this->encodeOptions(),
1446 'user_touched' => $dbw->timestamp($this->mTouched),
1447 'user_token' => $this->mToken
1448 ), array( /* WHERE */
1449 'user_id' => $this->mId
1450 ), $fname
1451 );
1452 $wgMemc->delete( "$wgDBname:user:id:$this->mId" );
1453 }
1454
1455
1456 /**
1457 * Checks if a user with the given name exists, returns the ID
1458 */
1459 function idForName() {
1460 $fname = 'User::idForName';
1461
1462 $gotid = 0;
1463 $s = trim( $this->getName() );
1464 if ( 0 == strcmp( '', $s ) ) return 0;
1465
1466 $dbr =& wfGetDB( DB_SLAVE );
1467 $id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), $fname );
1468 if ( $id === false ) {
1469 $id = 0;
1470 }
1471 return $id;
1472 }
1473
1474 /**
1475 * Add user object to the database
1476 */
1477 function addToDatabase() {
1478 $fname = 'User::addToDatabase';
1479 $dbw =& wfGetDB( DB_MASTER );
1480 $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
1481 $dbw->insert( 'user',
1482 array(
1483 'user_id' => $seqVal,
1484 'user_name' => $this->mName,
1485 'user_password' => $this->mPassword,
1486 'user_newpassword' => $this->mNewpassword,
1487 'user_email' => $this->mEmail,
1488 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
1489 'user_real_name' => $this->mRealName,
1490 'user_options' => $this->encodeOptions(),
1491 'user_token' => $this->mToken,
1492 'user_registration' => $dbw->timestamp( $this->mRegistration ),
1493 ), $fname
1494 );
1495 $this->mId = $dbw->insertId();
1496 }
1497
1498 function spreadBlock() {
1499 # If the (non-anonymous) user is blocked, this function will block any IP address
1500 # that they successfully log on from.
1501 $fname = 'User::spreadBlock';
1502
1503 wfDebug( "User:spreadBlock()\n" );
1504 if ( $this->mId == 0 ) {
1505 return;
1506 }
1507
1508 $userblock = Block::newFromDB( '', $this->mId );
1509 if ( !$userblock->isValid() ) {
1510 return;
1511 }
1512
1513 # Check if this IP address is already blocked
1514 $ipblock = Block::newFromDB( wfGetIP() );
1515 if ( $ipblock->isValid() ) {
1516 # If the user is already blocked. Then check if the autoblock would
1517 # excede the user block. If it would excede, then do nothing, else
1518 # prolong block time
1519 if ($userblock->mExpiry &&
1520 ($userblock->mExpiry < Block::getAutoblockExpiry($ipblock->mTimestamp))) {
1521 return;
1522 }
1523 # Just update the timestamp
1524 $ipblock->updateTimestamp();
1525 return;
1526 }
1527
1528 # Make a new block object with the desired properties
1529 wfDebug( "Autoblocking {$this->mName}@" . wfGetIP() . "\n" );
1530 $ipblock->mAddress = wfGetIP();
1531 $ipblock->mUser = 0;
1532 $ipblock->mBy = $userblock->mBy;
1533 $ipblock->mReason = wfMsg( 'autoblocker', $this->getName(), $userblock->mReason );
1534 $ipblock->mTimestamp = wfTimestampNow();
1535 $ipblock->mAuto = 1;
1536 # If the user is already blocked with an expiry date, we don't
1537 # want to pile on top of that!
1538 if($userblock->mExpiry) {
1539 $ipblock->mExpiry = min ( $userblock->mExpiry, Block::getAutoblockExpiry( $ipblock->mTimestamp ));
1540 } else {
1541 $ipblock->mExpiry = Block::getAutoblockExpiry( $ipblock->mTimestamp );
1542 }
1543
1544 # Insert it
1545 $ipblock->insert();
1546
1547 }
1548
1549 /**
1550 * Generate a string which will be different for any combination of
1551 * user options which would produce different parser output.
1552 * This will be used as part of the hash key for the parser cache,
1553 * so users will the same options can share the same cached data
1554 * safely.
1555 *
1556 * Extensions which require it should install 'PageRenderingHash' hook,
1557 * which will give them a chance to modify this key based on their own
1558 * settings.
1559 *
1560 * @return string
1561 */
1562 function getPageRenderingHash() {
1563 global $wgContLang;
1564 if( $this->mHash ){
1565 return $this->mHash;
1566 }
1567
1568 // stubthreshold is only included below for completeness,
1569 // it will always be 0 when this function is called by parsercache.
1570
1571 $confstr = $this->getOption( 'math' );
1572 $confstr .= '!' . $this->getOption( 'stubthreshold' );
1573 $confstr .= '!' . $this->getOption( 'date' );
1574 $confstr .= '!' . ($this->getOption( 'numberheadings' ) ? '1' : '');
1575 $confstr .= '!' . $this->getOption( 'language' );
1576 $confstr .= '!' . $this->getOption( 'thumbsize' );
1577 // add in language specific options, if any
1578 $extra = $wgContLang->getExtraHashOptions();
1579 $confstr .= $extra;
1580
1581 // Give a chance for extensions to modify the hash, if they have
1582 // extra options or other effects on the parser cache.
1583 wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
1584
1585 $this->mHash = $confstr;
1586 return $confstr;
1587 }
1588
1589 function isAllowedToCreateAccount() {
1590 return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
1591 }
1592
1593 /**
1594 * Set mDataLoaded, return previous value
1595 * Use this to prevent DB access in command-line scripts or similar situations
1596 */
1597 function setLoaded( $loaded ) {
1598 return wfSetVar( $this->mDataLoaded, $loaded );
1599 }
1600
1601 /**
1602 * Get this user's personal page title.
1603 *
1604 * @return Title
1605 * @public
1606 */
1607 function getUserPage() {
1608 return Title::makeTitle( NS_USER, $this->getName() );
1609 }
1610
1611 /**
1612 * Get this user's talk page title.
1613 *
1614 * @return Title
1615 * @public
1616 */
1617 function getTalkPage() {
1618 $title = $this->getUserPage();
1619 return $title->getTalkPage();
1620 }
1621
1622 /**
1623 * @static
1624 */
1625 function getMaxID() {
1626 static $res; // cache
1627
1628 if ( isset( $res ) )
1629 return $res;
1630 else {
1631 $dbr =& wfGetDB( DB_SLAVE );
1632 return $res = $dbr->selectField( 'user', 'max(user_id)', false, 'User::getMaxID' );
1633 }
1634 }
1635
1636 /**
1637 * Determine whether the user is a newbie. Newbies are either
1638 * anonymous IPs, or the most recently created accounts.
1639 * @return bool True if it is a newbie.
1640 */
1641 function isNewbie() {
1642 return !$this->isAllowed( 'autoconfirmed' );
1643 }
1644
1645 /**
1646 * Check to see if the given clear-text password is one of the accepted passwords
1647 * @param string $password User password.
1648 * @return bool True if the given password is correct otherwise False.
1649 */
1650 function checkPassword( $password ) {
1651 global $wgAuth, $wgMinimalPasswordLength;
1652 $this->loadFromDatabase();
1653
1654 // Even though we stop people from creating passwords that
1655 // are shorter than this, doesn't mean people wont be able
1656 // to. Certain authentication plugins do NOT want to save
1657 // domain passwords in a mysql database, so we should
1658 // check this (incase $wgAuth->strict() is false).
1659 if( strlen( $password ) < $wgMinimalPasswordLength ) {
1660 return false;
1661 }
1662
1663 if( $wgAuth->authenticate( $this->getName(), $password ) ) {
1664 return true;
1665 } elseif( $wgAuth->strict() ) {
1666 /* Auth plugin doesn't allow local authentication */
1667 return false;
1668 }
1669 $ep = $this->encryptPassword( $password );
1670 if ( 0 == strcmp( $ep, $this->mPassword ) ) {
1671 return true;
1672 } elseif ( ($this->mNewpassword != '') && (0 == strcmp( $ep, $this->mNewpassword )) ) {
1673 return true;
1674 } elseif ( function_exists( 'iconv' ) ) {
1675 # Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
1676 # Check for this with iconv
1677 $cp1252hash = $this->encryptPassword( iconv( 'UTF-8', 'WINDOWS-1252', $password ) );
1678 if ( 0 == strcmp( $cp1252hash, $this->mPassword ) ) {
1679 return true;
1680 }
1681 }
1682 return false;
1683 }
1684
1685 /**
1686 * Initialize (if necessary) and return a session token value
1687 * which can be used in edit forms to show that the user's
1688 * login credentials aren't being hijacked with a foreign form
1689 * submission.
1690 *
1691 * @param mixed $salt - Optional function-specific data for hash.
1692 * Use a string or an array of strings.
1693 * @return string
1694 * @public
1695 */
1696 function editToken( $salt = '' ) {
1697 if( !isset( $_SESSION['wsEditToken'] ) ) {
1698 $token = $this->generateToken();
1699 $_SESSION['wsEditToken'] = $token;
1700 } else {
1701 $token = $_SESSION['wsEditToken'];
1702 }
1703 if( is_array( $salt ) ) {
1704 $salt = implode( '|', $salt );
1705 }
1706 return md5( $token . $salt );
1707 }
1708
1709 /**
1710 * Generate a hex-y looking random token for various uses.
1711 * Could be made more cryptographically sure if someone cares.
1712 * @return string
1713 */
1714 function generateToken( $salt = '' ) {
1715 $token = dechex( mt_rand() ) . dechex( mt_rand() );
1716 return md5( $token . $salt );
1717 }
1718
1719 /**
1720 * Check given value against the token value stored in the session.
1721 * A match should confirm that the form was submitted from the
1722 * user's own login session, not a form submission from a third-party
1723 * site.
1724 *
1725 * @param string $val - the input value to compare
1726 * @param string $salt - Optional function-specific data for hash
1727 * @return bool
1728 * @public
1729 */
1730 function matchEditToken( $val, $salt = '' ) {
1731 global $wgMemc;
1732 $sessionToken = $this->editToken( $salt );
1733 if ( $val != $sessionToken ) {
1734 wfDebug( "User::matchEditToken: broken session data\n" );
1735 }
1736 return $val == $sessionToken;
1737 }
1738
1739 /**
1740 * Generate a new e-mail confirmation token and send a confirmation
1741 * mail to the user's given address.
1742 *
1743 * @return mixed True on success, a WikiError object on failure.
1744 */
1745 function sendConfirmationMail() {
1746 global $wgContLang;
1747 $url = $this->confirmationTokenUrl( $expiration );
1748 return $this->sendMail( wfMsg( 'confirmemail_subject' ),
1749 wfMsg( 'confirmemail_body',
1750 wfGetIP(),
1751 $this->getName(),
1752 $url,
1753 $wgContLang->timeanddate( $expiration, false ) ) );
1754 }
1755
1756 /**
1757 * Send an e-mail to this user's account. Does not check for
1758 * confirmed status or validity.
1759 *
1760 * @param string $subject
1761 * @param string $body
1762 * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
1763 * @return mixed True on success, a WikiError object on failure.
1764 */
1765 function sendMail( $subject, $body, $from = null ) {
1766 if( is_null( $from ) ) {
1767 global $wgPasswordSender;
1768 $from = $wgPasswordSender;
1769 }
1770
1771 require_once( 'UserMailer.php' );
1772 $to = new MailAddress( $this );
1773 $sender = new MailAddress( $from );
1774 $error = userMailer( $to, $sender, $subject, $body );
1775
1776 if( $error == '' ) {
1777 return true;
1778 } else {
1779 return new WikiError( $error );
1780 }
1781 }
1782
1783 /**
1784 * Generate, store, and return a new e-mail confirmation code.
1785 * A hash (unsalted since it's used as a key) is stored.
1786 * @param &$expiration mixed output: accepts the expiration time
1787 * @return string
1788 * @private
1789 */
1790 function confirmationToken( &$expiration ) {
1791 $fname = 'User::confirmationToken';
1792
1793 $now = time();
1794 $expires = $now + 7 * 24 * 60 * 60;
1795 $expiration = wfTimestamp( TS_MW, $expires );
1796
1797 $token = $this->generateToken( $this->mId . $this->mEmail . $expires );
1798 $hash = md5( $token );
1799
1800 $dbw =& wfGetDB( DB_MASTER );
1801 $dbw->update( 'user',
1802 array( 'user_email_token' => $hash,
1803 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
1804 array( 'user_id' => $this->mId ),
1805 $fname );
1806
1807 return $token;
1808 }
1809
1810 /**
1811 * Generate and store a new e-mail confirmation token, and return
1812 * the URL the user can use to confirm.
1813 * @param &$expiration mixed output: accepts the expiration time
1814 * @return string
1815 * @private
1816 */
1817 function confirmationTokenUrl( &$expiration ) {
1818 $token = $this->confirmationToken( $expiration );
1819 $title = Title::makeTitle( NS_SPECIAL, 'Confirmemail/' . $token );
1820 return $title->getFullUrl();
1821 }
1822
1823 /**
1824 * Mark the e-mail address confirmed and save.
1825 */
1826 function confirmEmail() {
1827 $this->loadFromDatabase();
1828 $this->mEmailAuthenticated = wfTimestampNow();
1829 $this->saveSettings();
1830 return true;
1831 }
1832
1833 /**
1834 * Is this user allowed to send e-mails within limits of current
1835 * site configuration?
1836 * @return bool
1837 */
1838 function canSendEmail() {
1839 return $this->isEmailConfirmed();
1840 }
1841
1842 /**
1843 * Is this user allowed to receive e-mails within limits of current
1844 * site configuration?
1845 * @return bool
1846 */
1847 function canReceiveEmail() {
1848 return $this->canSendEmail() && !$this->getOption( 'disablemail' );
1849 }
1850
1851 /**
1852 * Is this user's e-mail address valid-looking and confirmed within
1853 * limits of the current site configuration?
1854 *
1855 * If $wgEmailAuthentication is on, this may require the user to have
1856 * confirmed their address by returning a code or using a password
1857 * sent to the address from the wiki.
1858 *
1859 * @return bool
1860 */
1861 function isEmailConfirmed() {
1862 global $wgEmailAuthentication;
1863 $this->loadFromDatabase();
1864 $confirmed = true;
1865 if( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
1866 if( $this->isAnon() )
1867 return false;
1868 if( !$this->isValidEmailAddr( $this->mEmail ) )
1869 return false;
1870 if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
1871 return false;
1872 return true;
1873 } else {
1874 return $confirmed;
1875 }
1876 }
1877
1878 /**
1879 * @param array $groups list of groups
1880 * @return array list of permission key names for given groups combined
1881 * @static
1882 */
1883 function getGroupPermissions( $groups ) {
1884 global $wgGroupPermissions;
1885 $rights = array();
1886 foreach( $groups as $group ) {
1887 if( isset( $wgGroupPermissions[$group] ) ) {
1888 $rights = array_merge( $rights,
1889 array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
1890 }
1891 }
1892 return $rights;
1893 }
1894
1895 /**
1896 * @param string $group key name
1897 * @return string localized descriptive name for group, if provided
1898 * @static
1899 */
1900 function getGroupName( $group ) {
1901 $key = "group-$group";
1902 $name = wfMsg( $key );
1903 if( $name == '' || $name == "&lt;$key&gt;" ) {
1904 return $group;
1905 } else {
1906 return $name;
1907 }
1908 }
1909
1910 /**
1911 * @param string $group key name
1912 * @return string localized descriptive name for member of a group, if provided
1913 * @static
1914 */
1915 function getGroupMember( $group ) {
1916 $key = "group-$group-member";
1917 $name = wfMsg( $key );
1918 if( $name == '' || $name == "&lt;$key&gt;" ) {
1919 return $group;
1920 } else {
1921 return $name;
1922 }
1923 }
1924
1925
1926 /**
1927 * Return the set of defined explicit groups.
1928 * The * and 'user' groups are not included.
1929 * @return array
1930 * @static
1931 */
1932 function getAllGroups() {
1933 global $wgGroupPermissions;
1934 return array_diff(
1935 array_keys( $wgGroupPermissions ),
1936 array( '*', 'user', 'autoconfirmed' ) );
1937 }
1938
1939 /**
1940 * Get the title of a page describing a particular group
1941 *
1942 * @param $group Name of the group
1943 * @return mixed
1944 */
1945 function getGroupPage( $group ) {
1946 $page = wfMsgForContent( 'grouppage-' . $group );
1947 if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) {
1948 $title = Title::newFromText( $page );
1949 if( is_object( $title ) )
1950 return $title;
1951 }
1952 return false;
1953 }
1954
1955
1956 }
1957
1958 ?>