Wikia code/includes/User.php

From mediawiki.org

Some removals of un-used Wikia core-hacks: http://dev.wikia.com/wiki/Special:Code/Wikia_Trunk/44954

Another simple removal: http://dev.wikia.com/wiki/Special:Code/Wikia_Trunk/44955

--- D:\Programming\SVN\mediawiki\branches\REL1_16\phase3\includes\User.php	2011-07-18 22:31:27.990234400 +0100
+++ D:\Programming\SVN\wikia\trunk\includes\User.php	2011-08-17 15:28:46.352539100 +0100
@@ -90,6 +90,8 @@
 		'ccmeonemails',
 		'diffonly',
 		'showhiddencats',
+		'searchsuggest', # Wikia
+		'htmlemails', # Wikia
 		'noconvertlink',
 		'norollbackdiff',
 	);
@@ -212,6 +214,7 @@
 
 	/** @name Lazy-initialized variables, invalidated with clearInstanceCache */
 	//@{
+	var $mTheme; # Wikia - Skin chooser related
 	var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights,
 		$mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally,
 		$mLocked, $mHideName, $mOptions;
@@ -277,7 +280,7 @@
 	 * @private
 	 */
 	function loadFromId() {
-		global $wgMemc;
+		global $wgMemc, $wgSharedDB; # Wikia
 		if ( $this->mId == 0 ) {
 			$this->loadDefaults();
 			return false;
@@ -291,7 +294,26 @@
 			$data = false;
 		}
 
-		if ( !$data ) {
+		$isExpired = false;
+		if( !empty( $wgSharedDB ) ) {
+			# Wikia
+			/*
+			 * This code is responsible for re-invalidate user object data from database
+			 * instead of memcache if user preferences had been changed on another wiki
+			 */
+			$isExpired = true;
+			if(!empty($data)) {
+				$_key = wfSharedMemcKey( "user_touched", $this->mId );
+				$_touched = $wgMemc->get( $_key );
+				if($_touched == null){
+					$wgMemc->set( $_key, $data['mTouched'] );
+				} else if( $_touched <= $data['mTouched'] ) {
+					$isExpired = false;
+				}
+			}
+		}
+
+		if ( !$data || $isExpired ) { # Wikia
 			wfDebug( "Cache miss for user {$this->mId}\n" );
 			# Load from DB
 			if ( !$this->loadFromDatabase() ) {
@@ -303,9 +325,11 @@
 			wfDebug( "Got user {$this->mId} from cache\n" );
 			# Restore from cache
 			foreach ( self::$mCacheVars as $name ) {
+				if( isset( $data[$name] ) ) {
 				$this->$name = $data[$name];
 			}
 		}
+		}
 		return true;
 	}
 
@@ -470,6 +494,22 @@
 		$s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), __METHOD__ );
 
 		if ( $s === false ) {
+			$user_name = $nt->getText();
+			wfRunHooks( 'UserNameLoadFromId', array( $user_name, &$s ) );
+		}
+		
+		/* wikia change */
+		if ( $s === false ) {
+			global $wgExternalAuthType;
+			if ( $wgExternalAuthType ) {
+				$mExtUser = ExternalUser::newFromName( $nt->getText() );
+				if ( is_object( $mExtUser ) && ( 0 != $mExtUser->getId() ) ) {
+					$mExtUser->linkToLocal( $mExtUser->getId() );
+				}
+			}
+		}
+
+		if ( $s === false ) {
 			$result = null;
 		} else {
 			$result = $s->user_id;
@@ -610,10 +650,41 @@
 	static function isCreatableName( $name ) {
 		global $wgInvalidUsernameCharacters;
 		return
-			self::isUsableName( $name ) &&
+			self::isUsableName( $name ) && self::isInvalidUsernameCharacters( $name ) && self::isNotMaxNameChars( $name );
+	}
+
 
-			// Registration-time character blacklisting...
-			!preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name );
+	/**
+	 * Check for Invalid Characters inside user name
+	 * * @param $name \string String to match
+	 * @return \bool True or false
+	 */
+
+	static function isInvalidUsernameCharacters($name) {
+		global $wgInvalidUsernameCharacters;
+		if ( !empty( $wgInvalidUsernameCharacters ) ) {
+			return !preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name );
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * Check for max name length
+	 * * @param $name \string String to match
+	 * @return \bool True or false
+	 */
+
+	static function isNotMaxNameChars($name) {
+		global $wgWikiaMaxNameChars;
+
+		if( empty($wgWikiaMaxNameChars) ) {
+			//emergency fallback
+			global $wgMaxNameChars;
+			$wgWikiaMaxNameChars = $wgMaxNameChars;
+		}
+
+		return !( mb_strlen($name) > $wgWikiaMaxNameChars );
 	}
 
 	/**
@@ -677,8 +748,8 @@
 		if( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
 			return $result;
 		}
-
-		return strpos( $addr, '@' ) !== false;
+		//switch again to regular expression - it can however reject rarely used TLDs like .museum - Marooned at Wikia.com
+		return preg_match('/^[a-z0-9._%+-]+@(?:[a-z0-9\-]+\.)+[a-z]{2,4}$/i', $addr) !== 0;
 	}
 
 	/**
@@ -829,6 +900,8 @@
 		$this->mEmailTokenExpires = null;
 		$this->mRegistration = wfTimestamp( TS_MW );
 		$this->mGroups = array();
+		$this->mMonacoData = null;
+		$this->mMonacoSidebar = null;
 
 		wfRunHooks( 'UserLoadDefaults', array( $this, $name ) );
 
@@ -857,14 +930,6 @@
 			return $result;
 		}
 
-		if ( $wgExternalAuthType && $wgAutocreatePolicy == 'view' ) {
-			$extUser = ExternalUser::newFromCookie();
-			if ( $extUser ) {
-				# TODO: Automatically create the user here (or probably a bit
-				# lower down, in fact)
-			}
-		}
-
 		if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) {
 			$sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
 			if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) {
@@ -896,6 +961,14 @@
 			return false;
 		}
 
+		// move it lower down
+		if ( $wgExternalAuthType && $wgAutocreatePolicy == 'view' ) {
+			$extUser = ExternalUser::newFromCookie();
+			if ( $extUser ) {
+				$extUser->linkToLocal( $sId );
+			}
+		}
+
 		$passwordCorrect = FALSE;
 		$proposedUser = User::newFromId( $sId );
 		if ( !$proposedUser->isLoggedIn() ) {
@@ -927,6 +1000,7 @@
 			$this->loadFromUserObject( $proposedUser );
 			$_SESSION['wsToken'] = $this->mToken;
 			wfDebug( "Logged in from $from\n" );
+			wfRunHooks( 'UserLoadFromSessionInfo', array( $this, $from ) );
 			return true;
 		} else {
 			# Invalid credentials
@@ -966,6 +1040,11 @@
 		}
 
 		$dbr = wfGetDB( DB_MASTER );
+		if ( !is_object( $dbr ) ) {
+			$this->loadDefaults();
+			return false;
+		}
+
 		$s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ );
 
 		wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
@@ -975,6 +1054,8 @@
 			$this->loadFromRow( $s );
 			$this->mGroups = null; // deferred
 			$this->getEditCount(); // revalidation for nulls
+			$this->mMonacoData = null;
+			$this->mMonacoSidebar = null;
 			return true;
 		} else {
 			# Invalid user_id
@@ -1027,6 +1108,7 @@
 				$this->mGroups[] = $row->ug_group;
 			}
 		}
+		wfRunHooks( 'UserLoadGroups', array( $this ) );
 	}
 
 	/**
@@ -1041,6 +1123,7 @@
 		$this->mBlockedby = -1; # Unset
 		$this->mHash = false;
 		$this->mSkin = null;
+		$this->mTheme = null; # Wikia - Skin chooser related
 		$this->mRights = null;
 		$this->mEffectiveGroups = null;
 		$this->mOptions = null;
@@ -1101,6 +1184,9 @@
 	static function getToggles() {
 		global $wgContLang, $wgUseRCPatrol;
 		$extraToggles = array();
+		/* Wikia change begin - @author: ADi */
+		$extraToggles[] = 'marketingallowed';
+		/* Wikia change end */
 		wfRunHooks( 'UserToggles', array( &$extraToggles ) );
 		if( $wgUseRCPatrol ) {
 			$extraToggles[] = 'hidepatrolled';
@@ -1201,6 +1287,11 @@
 		# Extensions
 		wfRunHooks( 'GetBlockedStatus', array( &$this ) );
 
+		if ( !empty($this->mBlockedby) ) {
+		    $this->mBlock->mBy = $this->mBlockedby;
+		    $this->mBlock->mReason = $this->mBlockreason;
+        }
+
 		wfProfileOut( __METHOD__ );
 	}
 
@@ -1588,6 +1679,14 @@
 			# Check memcached separately for anons, who have no
 			# entire User object stored in there.
 			if( !$this->mId ) {
+				# hack: don't check it for our varnish ip addresses
+				global $wgSquidServers, $wgSquidServersNoPurge;
+				if( in_array( $this->getName(), $wgSquidServers ) ||
+					in_array( $this->getName(), $wgSquidServersNoPurge )
+				) {
+					return $this->mNewtalk;
+				}
+
 				global $wgMemc;
 				$key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
 				$newtalk = $wgMemc->get( $key );
@@ -1613,14 +1712,21 @@
 	 */
 	function getNewMessageLinks() {
 		$talks = array();
-		if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) )
-			return $talks;
+		wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks) );
 
-		if( !$this->getNewtalk() )
-			return array();
+		/* Wikia change begin - @author: XXX */
+		if( $this->getNewtalk() ) {
+			global $wgCityId, $wgSitename;
 		$up = $this->getUserPage();
 		$utp = $up->getTalkPage();
-		return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL() ) );
+			unset( $talks[$wgCityId] );
+			$talks[0] = array( "wiki" => $wgSitename, "link" => $utp->getFullURL() );
+			return $talks;
+		}
+		else {
+			return array_values($talks);
+		}
+		/* Wikia change end */
 	}
 
 	/**
@@ -1730,7 +1836,7 @@
 	 * user_touched field when we update things.
 	 * @return \string Timestamp in TS_MW format
 	 */
-	private static function newTouchedTimestamp() {
+	public static function newTouchedTimestamp() {
 		global $wgClockSkewFudge;
 		return wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
 	}
@@ -1745,8 +1851,10 @@
 	private function clearSharedCache() {
 		$this->load();
 		if( $this->mId ) {
-			global $wgMemc;
+			global $wgMemc, $wgSharedDB; # Wikia
 			$wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
+			# not uncyclo
+			if( !empty( $wgSharedDB ) ) $wgMemc->delete( wfSharedMemcKey( "user_touched", $this->mId ) ); # Wikia
 		}
 	}
 
@@ -1760,6 +1868,7 @@
 			return;
 		}
 		$this->load();
+		if ( wfReadOnly() ) { return; }
 		if( $this->mId ) {
 			$this->mTouched = self::newTouchedTimestamp();
 
@@ -1842,7 +1951,9 @@
 			// Save an invalid hash...
 			$this->mPassword = '';
 		} else {
-			$this->mPassword = self::crypt( $str );
+			// Wikia uses the old hashing until we migrate all wikis to MW 1.13
+			//$this->mPassword = self::crypt( $str );
+			$this->mPassword = self::oldCrypt( $str, $this->mId );
 		}
 		$this->mNewpassword = '';
 		$this->mNewpassTime = null;
@@ -1900,7 +2011,9 @@
 	 */
 	function setNewpassword( $str, $throttle = true ) {
 		$this->load();
-		$this->mNewpassword = self::crypt( $str );
+		// Wikia uses the old hashing until we migrate all wikis to MW 1.13
+		//$this->mNewpassword = self::crypt( $str );
+		$this->mNewpassword = self::oldCrypt( $str, $this->mId );
 		if ( $throttle ) {
 			$this->mNewpassTime = wfTimestampNow();
 		}
@@ -1948,6 +2061,14 @@
 	function setEmail( $str ) {
 		$this->load();
 		$this->mEmail = $str;
+
+		/* Wikia change begin - @author: Macbre */
+		/* invalidate empty email - RT #44046 */
+		if ($str == '') {
+			$this->invalidateEmail();
+		}
+		/* Wikia change end */
+
 		wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
 	}
 
@@ -1989,7 +2110,26 @@
 		}
 
 		if ( array_key_exists( $oname, $this->mOptions ) ) {
-			return $this->mOptions[$oname];
+			# Wikia - Skin chooser related
+			if($oname == 'skin') {
+				if(strlen(trim($this->mOptions[$oname])) > 7 &&  substr(trim($this->mOptions[$oname]), 0, 6) == 'quartz') {
+					$this->mOptions[$oname] = 'quartz';
+					$this->setOption('theme', substr(trim($this->mOptions[$oname]), 6));
+				} else if(trim($this->mOptions[$oname]) == 'slate' || trim($this->mOptions[$oname]) == 'smoke') {
+					$this->mOptions[$oname] = 'quartz';
+					$this->setOption('theme', trim($this->mOptions[$oname]));
+				}
+			}
+
+			/* Wikia change begin - @author: Macbre */
+			/* allow extensions to modify value returned by User::getOption() */
+			/* make local copy of option value, so hook won't modify value read from DB and store in object */
+			$value = $this->mOptions[$oname];
+
+			wfRunHooks( 'UserGetOption', array( $this->mOptions, $oname, &$value ) );
+
+			return $value;
+			/* Wikia change end */
 		} else {
 			return $defaultOverride;
 		}
@@ -2047,7 +2187,18 @@
 			# Clear cached skin, so the new one displays immediately in Special:Preferences
 			unset( $this->mSkin );
 		}
-
+		# Wikia - Skin chooser related
+		if($oname == 'theme'){
+			# Clear cached skin, so the new one displays immediately in Special:Preferences
+			unset($this->mTheme);
+		}
+		// Filter out any newlines that may have passed through input validation.
+		// Newlines are used to separate items in the options blob.
+		if( $val ) {
+			$val = str_replace( "\r\n", "\n", $val );
+			$val = str_replace( "\r", "\n", $val );
+			$val = str_replace( "\n", " ", $val );
+		}
 		// Explicitly NULL values should refer to defaults
 		global $wgDefaultUserOptions;
 		if( is_null( $val ) && isset( $wgDefaultUserOptions[$oname] ) ) {
@@ -2270,6 +2421,7 @@
 		if ( !isset( $this->mSkin ) ) {
 			wfProfileIn( __METHOD__ );
 
+			/*
 			global $wgHiddenPrefs;
 			if( !in_array( 'skin', $wgHiddenPrefs ) ) {
 				# get the user skin
@@ -2283,6 +2435,12 @@
 			}
 
 			$this->mSkin =& Skin::newFromKey( $userSkin );
+			*/
+
+			// Wikia change begin
+			wfRunHooks('AlternateGetSkin', array (&$this));
+			// Wikia change end
+
 			wfProfileOut( __METHOD__ );
 		}
 		if( $t || !$this->mSkin->getTitle() ) {
@@ -2372,16 +2530,8 @@
 		// If the page is watched by the user (or may be watched), update the timestamp on any
 		// any matching rows
 		if ( $watched ) {
-			$dbw = wfGetDB( DB_MASTER );
-			$dbw->update( 'watchlist',
-					array( /* SET */
-						'wl_notificationtimestamp' => null
-					), array( /* WHERE */
-						'wl_title' => $title->getDBkey(),
-						'wl_namespace' => $title->getNamespace(),
-						'wl_user' => $this->getID()
-					), __METHOD__
-			);
+			$wl = WatchedItem::fromUserTitle( $this, $title );
+			$wl->clearWatch();
 		}
 	}
 
@@ -2407,11 +2557,33 @@
 					'wl_user' => $currentUser
 				), __METHOD__
 			);
+
+			wfRunHooks( 'User::resetWatch', array ( $currentUser ) );
 		# 	We also need to clear here the "you have new message" notification for the own user_talk page
 		#	This is cleared one page view later in Article::viewUpdates();
 		}
 	}
 
+	/** Wikia Change - begin - bringing this function back (don't see any harm in it and it's used in a few places) **/
+	/**
+	 * Encode this user's options as a string
+	 * @return \string Encoded options
+	 * @private
+	 */
+	function encodeOptions() {
+		$this->load();
+		if ( is_null( $this->mOptions ) ) {
+			$this->mOptions = User::getDefaultOptions();
+		}
+		$a = array();
+		foreach ( $this->mOptions as $oname => $oval ) {
+			array_push( $a, $oname.'='.$oval );
+		}
+		$s = implode( "\n", $a );
+		return $s;
+	}
+	/** Wikia Change - end - bringing this function back (don't see any harm in it and it's used in a few places) **/
+
 	/**
 	 * Set this user's options from an encoded string
 	 * @param $str \string Encoded options to import
@@ -2528,6 +2700,29 @@
 
 		$this->mTouched = self::newTouchedTimestamp();
 
+		/**
+		 * @author Krzysztof Krzy┼╝aniak (eloy)
+		 * trap for BugId: 4013
+		 */
+		// wikia change begin
+		if( $this->mEmail == "devbox@wikia-inc.com" || $this->mEmail == "devbox+test@wikia-inc.com" ) {
+			// gather everything we know about request
+			global $wgCommandLineMode;
+			$log = "MOLI TRAP@devbox: ";
+			if( $wgCommandLineMode ) {
+				$log .= $argv[ 0 ];
+				openlog( "trap", LOG_PID | LOG_PERROR, LOG_LOCAL6 );
+				syslog( LOG_WARNING, "$log");
+				closelog();
+			}
+			else {
+				global $wgTitle;
+				$log .= $wgTitle->getFullUrl();
+				error_log( $log );
+			}
+		}
+		// wikia change end
+
 		$dbw = wfGetDB( DB_MASTER );
 		$dbw->update( 'user',
 			array( /* SET */
@@ -2552,17 +2747,26 @@
 
 		wfRunHooks( 'UserSaveSettings', array( $this ) );
 		$this->clearSharedCache();
+
+		# Wikia - bad style fix for #1531 - needs review if it is still needed
+		global $wgRequest;
+		$action = $wgRequest->getVal( 'action');
+		$commit = ( isset($action) && $action == 'ajax' );
+		if ( $commit === true ) {
+			$dbw->commit();
+		}
+
 		$this->getUserPage()->invalidateCache();
 	}
 
 	/**
 	 * If only this user's username is known, and it exists, return the user ID.
 	 */
-	function idForName() {
+	function idForName( $fromMaster = false ) {
 		$s = trim( $this->getName() );
 		if ( $s === '' ) return 0;
 
-		$dbr = wfGetDB( DB_SLAVE );
+		$dbr = ( $fromMaster ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
 		$id = $dbr->selectField( 'user', 'user_id', array( 'user_name' => $s ), __METHOD__ );
 		if ( $id === false ) {
 			$id = 0;
@@ -2615,6 +2819,12 @@
 		$dbw->insert( 'user', $fields, __METHOD__, array( 'IGNORE' ) );
 		if ( $dbw->affectedRows() ) {
 			$newUser = User::newFromId( $dbw->insertId() );
+			/**
+			 * wikia, increase number of registered users for wfIniStats
+			 */
+			global $wgMemc;
+			$wgMemc->incr( wfSharedMemcKey( "registered-users-number" ) );
+
 		} else {
 			$newUser = null;
 		}
@@ -2709,7 +2920,7 @@
 
 		// Give a chance for extensions to modify the hash, if they have
 		// extra options or other effects on the parser cache.
-		wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
+		wfRunHooks( 'PageRenderingHash', array( &$confstr/*Wikia change start*/, &$this/*Wikia change end*/ ) );
 
 		// Make it a valid memcached key fragment
 		$confstr = str_replace( ' ', '_', $confstr );
@@ -2831,6 +3042,30 @@
 				return true;
 			}
 		}
 		return false;
 	}
 
@@ -2924,7 +3159,7 @@
 	 *
 	 * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure.
 	 */
-	function sendConfirmationMail() {
+	function sendConfirmationMail($mailtype = "ConfirmationMail", $mailmsg = 'confirmemail', $ip_arg = true) {
 		global $wgLang;
 		$expiration = null; // gets passed-by-ref and defined in next line.
 		$token = $this->confirmationToken( $expiration );
@@ -2932,6 +3167,22 @@
 		$invalidateURL = $this->invalidationTokenUrl( $token );
 		$this->saveSettings();
 
+		$manualURL = SpecialPage::getTitleFor( 'ConfirmEmail/manual' )->getFullURL();
+
+		$IP = wfGetIP();
+		$name = $this->getName();
+		$expDate = $wgLang->timeanddate( $expiration, false );
+
+		if(!$ip_arg) {
+			$args = array($name, $url, $expDate, $invalidateURL, $manualURL, $token);
+		} else {
+			$args = array($IP, $name, $url, $expDate, $invalidateURL, $manualURL, $token);
+		}
+
+		/* Wikia change begin - @author: Marooned */
+		/* HTML e-mails functionality */
+		global $wgEnableRichEmails;
+		if (empty($wgEnableRichEmails)) {
 		return $this->sendMail( wfMsg( 'confirmemail_subject' ),
 			wfMsg( 'confirmemail_body',
 				wfGetIP(),
@@ -2940,7 +3191,40 @@
 				$wgLang->timeanddate( $expiration, false ),
 				$invalidateURL,
 				$wgLang->date( $expiration, false ),
-				$wgLang->time( $expiration, false ) ) );
+				$wgLang->time( $expiration, false ) ), null, null, $mailtype );
+		} else {
+			$wantHTML = $this->isAnon() || $this->getOption('htmlemails');
+
+			list($body, $bodyHTML) = wfMsgHTMLwithLanguage( $mailmsg.'_body', $this->getOption('language'), array(), $args, $wantHTML);
+
+			return $this->sendMail( wfMsg( $mailmsg.'_subject' ), $body, null, null, $mailtype, $bodyHTML );
+		}
+		/* Wikia change end */
+	}
+
+	/**
+	 * Confirmation after change the emial
+	 *
+	 * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure.
+	 */
+	function sendReConfirmationMail() {
+		$this->setOption("mail_edited","1");
+		$this->saveSettings();
+		return $this->sendConfirmationMail( 'ReConfirmationMail', 'reconfirmemail' );
+	}
+
+	/**
+	 * Confirmation reminder after 7 day
+	 *
+	 * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure.
+	 */
+	function sendConfirmationReminderMail() {
+		if( ($this->getOption("cr_mailed", 0) == 1) || ($this->getOption("mail_edited", 0) == 1) ) {
+			return false;
+		}
+		$this->setOption("cr_mailed","1");
+		$this->saveSettings();
+		return $this->sendConfirmationMail( 'ConfirmationReminder', 'confirmemailreminder', false );
 	}
 
 	/**
@@ -2951,9 +3235,11 @@
 	 * @param $body \string Message body
 	 * @param $from \string Optional From address; if unspecified, default $wgPasswordSender will be used
 	 * @param $replyto \string Reply-To address
+	 * @param $category \string type of e-mail used for statistics (added by Marooned @ Wikia)
+	 * @param $bodyHTML \string rich version of $body (added by Marooned @ Wikia)
 	 * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure
 	 */
-	function sendMail( $subject, $body, $from = null, $replyto = null ) {
+	function sendMail( $subject, $body, $from = null, $replyto = null, $category = 'unknown', $bodyHTML = null, $priority = 0 ) {
 		if( is_null( $from ) ) {
 			global $wgPasswordSender;
 			$from = $wgPasswordSender;
@@ -2961,7 +3247,17 @@
 
 		$to = new MailAddress( $this );
 		$sender = new MailAddress( $from );
-		return UserMailer::send( $to, $sender, $subject, $body, $replyto );
+
+		/* Wikia change begin - @author: Marooned */
+		/* HTML e-mails functionality */
+		global $wgEnableRichEmails;
+		$richMail = !empty($wgEnableRichEmails) && ($this->isAnon() || $this->getOption('htmlemails')) && !empty($bodyHTML);
+		if ($richMail) {
+			return UserMailer::sendHTML( $to, $sender, $subject, $body, $bodyHTML, $replyto, $category, $priority );
+		} else {
+			return UserMailer::send( $to, $sender, $subject, $body, $replyto, null, $category, $priority );
+		}
+		/* Wikia change end */
 	}
 
 	/**
@@ -3559,7 +3886,6 @@
 	 * @return \bool
 	 */
 	static function comparePasswords( $hash, $password, $userId = false ) {
-		$m = false;
 		$type = substr( $hash, 0, 3 );
 
 		$result = false;
@@ -3639,10 +3965,19 @@
 		} else {
 			wfDebug( "Loading options for user " . $this->getId() . " from database.\n" );
 			// Load from database
+			// wikia change, load always from first cluster when we use
+			// shared users database
+			// @author Krzysztof Krzy┼╝aniak (eloy)
+			global $wgExternalSharedDB, $wgSharedDB;
+			if( isset( $wgSharedDB ) ) {
+				$dbr = wfGetDB( DB_SLAVE, array(), $wgExternalSharedDB );
+			}
+			else {
 			$dbr = wfGetDB( DB_SLAVE );
+			}
 
 			$res = $dbr->select(
				'user_properties',
 				'*',
 				array( 'up_user' => $this->getId() ),
 				__METHOD__
@@ -3665,7 +4000,14 @@
 		$extuser = ExternalUser::newFromUser( $this );
 
 		$this->loadOptions();
+		// wikia change
+		global $wgExternalSharedDB, $wgSharedDB, $wgGlobalUserProperties;
+		if( isset( $wgSharedDB ) ) {
+			$dbw = wfGetDB( DB_MASTER, array(), $wgExternalSharedDB );
+		}
+		else {
 		$dbw = wfGetDB( DB_MASTER );
+		}
 
 		$insert_rows = array();
 
@@ -3678,6 +4020,12 @@
 
 		foreach( $saveOptions as $key => $value ) {
 			# Don't bother storing default values
+			# <Wikia>
+			if ( is_array( $wgGlobalUserProperties ) && in_array( $key, $wgGlobalUserProperties ) ) {
+				$insert_rows[] = array( 'up_user' => $this->getId(), 'up_property' => $key, 'up_value' => $value );
+			}
+			# </Wikia>
+			else {
 			if ( ( is_null( self::getDefaultOption( $key ) ) &&
 					!( $value === false || is_null($value) ) ) ||
 					$value != self::getDefaultOption( $key ) ) {
@@ -3687,6 +4035,7 @@
 						'up_value' => $value,
 					);
 			}
+			}
 			if ( $extuser && isset( $wgAllowPrefChange[$key] ) ) {
 				switch ( $wgAllowPrefChange[$key] ) {
 					case 'local':
@@ -3700,9 +4049,13 @@
 		}
 
 		$dbw->begin();
		$dbw->delete( 'user_properties', array( 'up_user' => $this->getId() ), __METHOD__ );
		$dbw->insert( 'user_properties', $insert_rows, __METHOD__ );
 		$dbw->commit();
+
+		if ( $extuser ) {
+			$extuser->updateUser();
+		}
 	}
 
 	/**