<?php
/**
 * This file implements the User class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link http://sourceforge.net/projects/evocms/}.
 *
 * @copyright (c)2003-2010 by Francois PLANQUE - {@link http://fplanque.net/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * {@internal License choice
 * - If you have received this file as part of a package, please find the license.txt file in
 *   the same folder or the closest folder above for complete license terms.
 * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
 *   then you must choose one of the following licenses before using the file:
 *   - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
 *   - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
 * }}
 *
 * {@internal Open Source relicensing agreement:
 * Daniel HAHLER grants Francois PLANQUE the right to license
 * Daniel HAHLER's contributions to this file and the b2evolution project
 * under any OSI approved OSS license (http://www.opensource.org/licenses/).
 * }}
 *
 * @package evocore
 *
 * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
 * @author fplanque: Francois PLANQUE
 * @author blueyed: Daniel HAHLER
 *
 * @version $Id: _user.class.php,v 1.75.2.11 2011/02/24 18:43:46 sam2kb Exp $
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


// DEBUG: (Turn switch on or off to log debug info for specified category)
$GLOBALS['debug_perms'] = false;


load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );

/**
 * User Class
 *
 * @package evocore
 */
class User extends DataObject
{
	var $login;
	var $pass;
	var $firstname;
	var $lastname;
	var $nickname;
	var $idmode;
	var $locale;
	var $email;
	var $url;
	var $icq;
	var $aim;
	var $msn;
	var $yim;
	var $ip;
	var $domain;
	var $browser;
	var $datecreated;
	var $level;
	var $avatar_file_ID;
	var $ctry_ID;

	/**
	 * Does the user accept messages?
	 * Options:
	 *		0 - NO,
	 *		1 - only private messages,
	 *		2 - only emails,
	 *		3 - private messages and emails
	 * This attribute can be asked with get_msgform_possibility(), accepts_pm(),
	 * and accepts_email() functions to get complete sense.
	 * This is because it's possible that user allows PM, but user group does not.
	 *
	 * @var boolean
	 */
	var $allow_msgform;
	var $notify;
	var $showonline;

	/**
	 * Has the user been validated (by email)?
	 * @var boolean
	 */
	var $validated;

	/**
	 * Number of posts by this user. Use get_num_posts() to access this (lazy filled).
	 * @var integer
	 * @access protected
	 */
	var $_num_posts;

	/**
	 * The ID of the (primary, currently only) group of the user.
	 * @var integer
	 */
	var $group_ID;

	/**
	 * Reference to group
	 * @see User::get_Group()
	 * @var Group
	 * @access protected
	 */
	var $Group;

	/**
	 * Country lazy filled
	 *
	 * @var country
	 */
	var $Country;

	/**
	 * Blog posts statuses permissions
	 */
	var $blog_post_statuses = array();

	/**
	 * Cache for perms.
	 * @access protected
	 * @var array
	 */
	var $cache_perms = array();


	/**
	 * User fields
	 */
	var $userfields = array();
	var $userfields_by_type = array();
	var $updated_fields = array();
	var $new_fields = array();

	/**
	 * Userfield defs
	 */
	var $userfield_defs;

	/**
	 * Constructor
	 *
	 * @param object DB row
	 */
	function User( $db_row = NULL )
	{
		global $default_locale, $Settings, $localtimenow;

		// Call parent constructor:
		parent::DataObject( 'T_users', 'user_', 'user_ID' );

		// blueyed> TODO: this will never get translated for the current User if he has another locale/lang set than default, because it gets adjusted AFTER instantiating him/her..
		//       Use a callback (get_delete_restrictions/get_delete_cascades) instead? Should be also better for performance!
		// fp> These settings should probably be merged with the global database description used by the installer/upgrader. However I'm not sure about how compelx plugins would be able to integrate then...
		$this->delete_restrictions = array(
				array( 'table'=>'T_blogs', 'fk'=>'blog_owner_user_ID', 'msg'=>T_('%d blogs owned by this user') ),
				array( 'table'=>'T_items__item', 'fk'=>'post_lastedit_user_ID', 'msg'=>T_('%d posts last edited by this user') ),
				array( 'table'=>'T_items__item', 'fk'=>'post_assigned_user_ID', 'msg'=>T_('%d posts assigned to this user') ),
				array( 'table'=>'T_links', 'fk'=>'link_creator_user_ID', 'msg'=>T_('%d links created by this user') ),
				array( 'table'=>'T_links', 'fk'=>'link_lastedit_user_ID', 'msg'=>T_('%d links last edited by this user') ),
				array( 'table'=>'T_messaging__message', 'fk'=>'msg_author_user_ID', 'msg'=>T_('The user has %d authored message(s)') ),
				array( 'table'=>'T_messaging__threadstatus', 'fk'=>'tsta_user_ID', 'msg'=>T_('The user is part of %d messaging thread(s)') ),
			);

		$this->delete_cascades = array(
				array( 'table'=>'T_users__usersettings', 'fk'=>'uset_user_ID', 'msg'=>T_('%d user settings on collections') ),
				array( 'table'=>'T_sessions', 'fk'=>'sess_user_ID', 'msg'=>T_('%d sessions opened by this user') ),
				array( 'table'=>'T_coll_user_perms', 'fk'=>'bloguser_user_ID', 'msg'=>T_('%d user permissions on blogs') ),
				array( 'table'=>'T_subscriptions', 'fk'=>'sub_user_ID', 'msg'=>T_('%d subscriptions') ),
				array( 'table'=>'T_items__item', 'fk'=>'post_creator_user_ID', 'msg'=>T_('%d posts created by this user') ),
			);

		if( $db_row == NULL )
		{ // Setting those object properties, which are not "NULL" in DB (MySQL strict mode):

			// echo 'Creating blank user';
			$this->set( 'login', 'login' );
			$this->set( 'pass', md5('pass') );
			$this->set( 'locale',
				isset( $Settings )
					? $Settings->get('default_locale') // TODO: (settings) use "new users template setting"
					: $default_locale );
			$this->set( 'email', '' );	// fp> TODO: this is an invalid value. Saving the object without a valid email should fail! (actually: it should be fixed by providing a valid email)
			$this->set( 'level', isset( $Settings ) ? $Settings->get('newusers_level') : 0 );
			if( isset($localtimenow) )
			{
				$this->set_datecreated( $localtimenow );
			}
			else
			{ // We don't know local time here!
				$this->set_datecreated( time() );
			}

			if( isset($Settings) )
			{ // Group for this user:
				$this->group_ID = $Settings->get('newusers_grp_ID');
			}

			// This attribute can be asked with get_msgform_possibility(), accepts_pm(), accepts_email() functions
			// Default value: Allow both
 			$this->set( 'allow_msgform', 3 );

 			$this->set( 'notify', 0 );
 			$this->set( 'showonline', 1 );
		}
		else
		{
			// echo 'Instanciating existing user';
			$this->ID = $db_row->user_ID;
			$this->login = $db_row->user_login;
			$this->pass = $db_row->user_pass;
			$this->firstname = $db_row->user_firstname;
			$this->lastname = $db_row->user_lastname;
			$this->nickname = $db_row->user_nickname;
			$this->idmode = $db_row->user_idmode;
			$this->locale = $db_row->user_locale;
			$this->email = $db_row->user_email;
			$this->url = $db_row->user_url;
			$this->icq = $db_row->user_icq;
			$this->aim = $db_row->user_aim;
			$this->msn = $db_row->user_msn;
			$this->yim = $db_row->user_yim;
			$this->ip = $db_row->user_ip;
			$this->domain = $db_row->user_domain;
			$this->browser = $db_row->user_browser;
			$this->datecreated = $db_row->dateYMDhour;
			$this->level = $db_row->user_level;
			$this->allow_msgform = $db_row->user_allow_msgform;
			$this->validated = $db_row->user_validated;
			$this->notify = $db_row->user_notify;
			$this->showonline = $db_row->user_showonline;
			$this->avatar_file_ID = $db_row->user_avatar_file_ID;
			$this->ctry_ID = $db_row->user_ctry_ID;

			// Group for this user:
			$this->group_ID = $db_row->user_grp_ID;
		}
	}


	/**
	 * Load data from Request form fields.
	 *
	 * @return boolean true if loaded data seems valid.
	 */
	function load_from_Request()
	{
		global $DB, $Settings, $UserSettings, $GroupCache, $Messages;
		global $current_User;

		$edited_user_login = param( 'edited_user_login', 'string' );
		param_check_not_empty( 'edited_user_login', T_( 'You must provide a login!' ) );
		// We want all logins to be lowercase to guarantee uniqueness regardless of the database case handling for UNIQUE indexes:
		$this->set_from_Request( 'login', 'edited_user_login', true, 'strtolower' );

		// ******* Identity form ******* //

		$is_identity_form = param( 'identity_form', 'boolean', false );

		if( $is_identity_form )
		{
			if( $current_User->check_perm( 'users', 'edit' ) )
			{// changing level/group is allowed (not in profile mode)

				param_integer_range( 'edited_user_level', 0, 10, T_('User level must be between %d and %d.') );
				$this->set_from_Request( 'level', 'edited_user_level', true );

				param( 'edited_user_validated', 'integer', 0 );
				$this->set_from_Request( 'validated', 'edited_user_validated', true );

				$edited_user_Group = $GroupCache->get_by_ID( param( 'edited_user_grp_ID', 'integer' ) );
				$this->set_Group( $edited_user_Group );
			}

			// check if new login already exists for another user_ID
			$query = '
				SELECT user_ID
				  FROM T_users
				 WHERE user_login = '.$DB->quote( $edited_user_login ).'
				   AND user_ID != '.$this->ID;

			if( $q = $DB->get_var( $query ) )
			{
				param_error( 'edited_user_login',
					sprintf( T_( 'This login already exists. Do you want to <a %s>edit the existing user</a>?' ),
						'href="?ctrl=user&amp;user_tab=identity&amp;user_ID='.$q.'"' ) );
			}

			// EXPERIMENTAL user fields & EXISTING fields:
			// Get indices of existing userfields:
			$userfield_IDs = $DB->get_col( '
						SELECT uf_ID
							FROM T_users__fields
						 WHERE uf_user_ID = '.$this->ID );

			foreach( $userfield_IDs as $userfield_ID )
			{
				$uf_val = param( 'uf_'.$userfield_ID, 'string', '' );

				// TODO: type checking
				$this->userfield_update( $userfield_ID, $uf_val );
			}

			// NEW fields:
			for( $i=1; $i<=3; $i++ )
			{	// new fields:
				$new_uf_type = param( 'new_uf_type_'.$i, 'integer', '' );
				$new_uf_val = param( 'new_uf_val_'.$i, 'string', '' );
				if( empty($new_uf_type) && empty($new_uf_val) )
				{
					continue;
				}

				if( empty($new_uf_type) )
				{
					param_error( 'new_uf_val_'.$i, T_('Please select a field type.') );
				}
				if( empty($new_uf_val) )
				{
					param_error( 'new_uf_val_'.$i, T_('Please enter a value.') );
				}

				// TODO: type checking
				$this->userfield_add( $new_uf_type, $new_uf_val );
			}

			param( 'edited_user_email', 'string', true );
			param_check_not_empty( 'edited_user_email', T_('Please enter an e-mail address.') );
			param_check_email( 'edited_user_email', true );
			$this->set_from_Request('email', 'edited_user_email', true);

			// set allow_msgform:
			// 0 - none,
			// 1 - only private message,
			// 2 - only email,
			// 3 - private message and email
			$allow_msgform = 0;
			if( param( 'PM', 'integer', 0 ) )
			{ // PM is enabled
				$allow_msgform = 1;
			}
			if( param( 'email', 'integer', 0 ) )
			{ // email is enabled
				$allow_msgform = $allow_msgform + 2;
			}
			$this->set( 'allow_msgform', $allow_msgform );

			param( 'edited_user_notify', 'integer', 0 );
			$this->set_from_Request('notify', 'edited_user_notify', true);

			param( 'edited_user_firstname', 'string', true );
			$this->set_from_Request('firstname', 'edited_user_firstname', true);

			param( 'edited_user_lastname', 'string', true );
			$this->set_from_Request('lastname', 'edited_user_lastname', true);

			if( $Settings->get( 'nickname_editing' ) == 'hidden' )
			{
				$this->set_from_Request('nickname', 'edited_user_login', true);
			}
			else
			{
				param( 'edited_user_nickname', 'string', true );
				param_check_not_empty( 'edited_user_nickname', T_('Please enter a nickname (can be the same as your login).') );
				$this->set_from_Request('nickname', 'edited_user_nickname', true);
			}

			param( 'edited_user_idmode', 'string', true );
			$this->set_from_Request('idmode', 'edited_user_idmode', true);

			param( 'edited_user_ctry_ID', 'integer', true );
			param_check_number( 'edited_user_ctry_ID', 'Please select a country', !$current_User->check_perm( 'users', 'edit' ) );
			$this->set_from_Request('ctry_ID', 'edited_user_ctry_ID', true);

			param( 'edited_user_showonline', 'integer', 0 );
			$this->set_from_Request('showonline', 'edited_user_showonline', true);

			param( 'edited_user_url', 'string', true );
			param_check_url( 'edited_user_url', 'commenting' );
			$this->set_from_Request('url', 'edited_user_url', true);

			param( 'edited_user_icq', 'string', true );
			param_check_number( 'edited_user_icq', T_('The ICQ UIN can only be a number, no letters allowed.') );
			$this->set_from_Request('icq', 'edited_user_icq', true);

			param( 'edited_user_aim', 'string', true );
			$this->set_from_Request('aim', 'edited_user_aim', true);

			param( 'edited_user_msn', 'string', true );
			param_check_email( 'edited_user_msn', false );
			$this->set_from_Request('msn', 'edited_user_msn', true);

			param( 'edited_user_yim', 'string', true );
			$this->set_from_Request('yim', 'edited_user_yim', true);
		}

		// ******* Password form ******* //

		$is_password_form = param( 'password_form', 'boolean', false );

		if( $is_password_form || ( $is_identity_form && $this->ID == 0 ) )
		{
			param( 'edited_user_pass1', 'string', true );
			$edited_user_pass2 = param( 'edited_user_pass2', 'string', true );

			if( param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen') ) )
			{ 	// We can set password
				$this->set( 'pass', md5( $edited_user_pass2 ) );
			}
		}

		// ******* Preferences form ******* //

		$is_preferences_form = param( 'preferences_form', 'boolean', false );

		if( $is_preferences_form )
		{
			param( 'edited_user_locale', 'string', true );
			$this->set_from_Request('locale', 'edited_user_locale', true);

			$UserSettings->set( 'admin_skin', param( 'edited_user_admin_skin', 'string' ), $this->ID );

			// Action icon params:
			param_integer_range( 'edited_user_action_icon_threshold', 1, 5, T_('The threshold must be between 1 and 5.') );
			$UserSettings->set( 'action_icon_threshold', param( 'edited_user_action_icon_threshold', 'integer', true ), $this->ID );

			param_integer_range( 'edited_user_action_word_threshold', 1, 5, T_('The threshold must be between 1 and 5.') );
			$UserSettings->set( 'action_word_threshold', param( 'edited_user_action_word_threshold', 'integer'), $this->ID );

			$UserSettings->set( 'display_icon_legend', param( 'edited_user_legend', 'integer', 0 ), $this->ID );

			// Set bozo validador activation
			$UserSettings->set( 'control_form_abortions', param( 'edited_user_bozo', 'integer', 0 ), $this->ID );

			// Focus on first
			$UserSettings->set( 'focus_on_first_input', param( 'edited_user_focusonfirst', 'integer', 0 ), $this->ID );

			// Results per page
			$edited_user_results_per_page = param( 'edited_user_results_per_page', 'integer', NULL );
			if( isset($edited_user_results_per_page) )
			{
				$UserSettings->set( 'results_per_page', $edited_user_results_per_page, $this->ID );
			}

			// Session timeout
			$edited_user_timeout_sessions = param( 'edited_user_timeout_sessions', 'string', NULL );
			if( isset( $edited_user_timeout_sessions ) && $current_User->check_perm( 'users', 'edit' ) )
			{
				switch( $edited_user_timeout_sessions )
				{
					case 'default':
						$UserSettings->set( 'timeout_sessions', NULL, $this->ID );
						break;
					case 'custom':
						$UserSettings->set( 'timeout_sessions', param_duration( 'timeout_sessions' ), $this->ID );
						break;
				}
			}
		}

		if( $is_preferences_form || ( $is_identity_form && $this->ID == 0 ) )
		{	// Multiple session
			$multiple_sessions = $Settings->get( 'multiple_sessions' );
			if( ( $multiple_sessions != 'adminset_default_no' && $multiple_sessions != 'adminset_default_yes' ) || $current_User->check_perm( 'users', 'edit' ) )
			{
				$UserSettings->set( 'login_multiple_sessions', param( 'edited_user_set_login_multiple_sessions', 'integer', 0 ), $this->ID );
			}
		}

		return ! param_errors_detected();
	}


	/**
	 * Get a param
	 *
	 * @param string the parameter
	 */
	function get( $parname )
	{
		switch( $parname )
		{
			case 'fullname':
				return trim($this->firstname.' '.$this->lastname);

			case 'preferredname':
				return $this->get_preferred_name();

			case 'num_posts':
				return $this->get_num_posts();

			default:
			// All other params:
				return parent::get( $parname );
		}
	}


	/**
	 * Get the name of the account with complete details for admin select lists
	 *
	 * @return string
	 */
	function get_account_name()
	{
		return $this->login.' - '.$this->firstname.' '.$this->lastname.' ('.$this->nickname.')';
	}


	/**
	 * Get link to User
	 *
	 * @return string
	 */
	function get_link( $params = array() )
	{
		// Make sure we are not missing any param:
		$params = array_merge( array(
				'format'       => 'htmlbody',
				'link_to'		   => 'userpage', // userurl userpage 'userurl>userpage'
				'link_text'    => 'preferredname',
				'link_rel'     => '',
				'link_class'   => '',
				'thumb_size'   => 'crop-32x32',
				'thumb_class'  => '',
			), $params );

		if( $params['link_text'] == 'avatar' )
		{
			$r = $this->get_avatar_imgtag( $params['thumb_size'], $params['thumb_class'] );
		}
		else
		{
			$r = $this->dget( 'preferredname', $params['format'] );
		}

		switch( $params['link_to'] )
		{
			case 'userpage':
			case 'userpage>userurl':
				$url = $this->get_userpage_url();
				break;

			case 'userurl':
				$url = $this->url;
				break;

			case 'userurl>userpage':
				// We give priority to user submitted url:
				if( strlen($this->url)>10 )
				{
					$url = $this->url;
				}
				else
				{
					$url = $this->get_userpage_url();
				}
				break;
		}

		if( !empty($url) )
		{
			$link = '<a href="'.$url.'"';
			if( !empty($params['link_rel']) )
			{
				$link .= ' rel="'.$params['link_rel'].'"';
			}
			if( !empty($params['link_class']) )
			{
				$link .= ' class="'.$params['link_class'].'"';
			}
			$r = $link.'>'.$r.'</a>';
		}

		return $r;
	}


	/**
	 * Get preferred name of the user, according to {@link User::$idmode}.
	 *
	 * @return string
	 */
	function get_preferred_name()
	{
		switch( $this->idmode )
		{
			case 'namefl':
				return parent::get('firstname').' '.parent::get('lastname');

			case 'namelf':
				return parent::get('lastname').' '.parent::get('firstname');

			default:
				return parent::get($this->idmode);
		}
	}


	/**
	 * Get Country object
	 */
	function & get_Country()
	{
		if( is_null($this->Country) && !empty($this->ctry_ID ) )
		{
			$CountryCache = & get_CountryCache();
			$this->Country = $CountryCache->get_by_ID( $this->ctry_ID );
		}

		return $this->Country;
	}


	/**
	 * Get country name
	 */
	function get_country_name()
	{
		if( $this->get_Country() )
		{	// We have a country:
			return $this->Country->name;
		}

		return 'UNKNOWN';
	}


	/**
	 * Get the number of posts for the user.
	 *
	 * @return integer
	 */
	function get_num_posts()
	{
		global $DB;
		global $collections_Module;

		if( isset($collections_Module) )
		{
			if( is_null( $this->_num_posts ) )
			{
				$this->_num_posts = $DB->get_var( 'SELECT count(*)
																					FROM T_items__item
																					WHERE post_creator_user_ID = '.$this->ID );
			}
		}

		return $this->_num_posts;
	}


	/**
	 * Get the path to the media directory. If it does not exist, it will be created.
	 *
	 * If we're {@link is_admin_page() on an admin page}, it adds status messages.
	 * @todo These status messages should rather go to a "syslog" and not be displayed to a normal user
	 * @todo dh> refactor this into e.g. create_media_dir() and use it for Blog::get_media_dir, too.
	 *
	 * @param boolean Create the directory, if it does not exist yet?
	 * @return mixed the path as string on success, false if the dir could not be created
	 */
	function get_media_dir( $create = true )
	{
		global $media_path, $Messages, $Settings, $Debuglog;

		if( ! $Settings->get( 'fm_enable_roots_user' ) )
		{	// User directories are disabled:
			$Debuglog->add( 'Attempt to access user media dir, but this feature is disabled', 'files' );
			return false;
		}

		$userdir = get_canonical_path( $media_path.'users/'.$this->login.'/' );

		if( $create && ! is_dir( $userdir ) )
		{
			if( ! is_writable( dirname($userdir) ) )
			{ // add error
				if( is_admin_page() )
				{
					$Messages->add( sprintf( T_("The user's media directory &laquo;%s&raquo; could not be created, because the parent directory is not writable or does not exist."), rel_path_to_base($userdir) )
							.get_manual_link('directory_creation_error'), 'error' );
				}
				return false;
			}
			elseif( !@mkdir( $userdir ) )
			{ // add error
				if( is_admin_page() )
				{
					$Messages->add( sprintf( T_("The user's media directory &laquo;%s&raquo; could not be created."), rel_path_to_base($userdir) )
							.get_manual_link('directory_creation_error'), 'error' );
				}
				return false;
			}
			else
			{ // chmod and add note:
				$chmod = $Settings->get('fm_default_chmod_dir');
				if( !empty($chmod) )
				{
					@chmod( $userdir, octdec($chmod) );
				}
				if( is_admin_page() )
				{
					$Messages->add( sprintf( T_("The user's directory &laquo;%s&raquo; has been created with permissions %s."), rel_path_to_base($userdir), substr( sprintf('%o', fileperms($userdir)), -3 ) ), 'success' );
				}
			}
		}
		return $userdir;
	}


	/**
	 * Get the URL to the media folder
	 *
	 * @return string the URL
	 */
	function get_media_url()
	{
		global $media_url, $Settings, $Debuglog;

		if( ! $Settings->get( 'fm_enable_roots_user' ) )
		{	// User directories are disabled:
			$Debuglog->add( 'Attempt to access user media URL, but this feature is disabled', 'files' );
			return false;
		}

		return $media_url.'users/'.$this->login.'/';
	}


  /**
	 * Get message form url
	 */
	function get_msgform_url( $formurl, $redirect_to = NULL )
	{
		global $ReqURI;

		if( ! $this->get_msgform_possibility() )
		{
			return NULL;
		}

		if( $redirect_to == NULL )
		{
			$redirect_to = $ReqURI;
		}

		return url_add_param( $formurl, 'recipient_id='.$this->ID.'&amp;redirect_to='.rawurlencode( $redirect_to ) );
	}


  /**
	 * Get user page url
	 */
	function get_userpage_url()
	{
	  /**
		 * @var Blog
		 */
		global $Blog;

		if( empty($Blog) )
		{
			return NULL;
		}

		$blogurl = $Blog->gen_blogurl();

		return url_add_param( $Blog->get('userurl'), 'user_ID='.$this->ID );
	}


	/**
	 * Set param value
	 *
	 * @param string parameter name
	 * @param mixed parameter value
	 * @param boolean true to set to NULL if empty value
	 * @return boolean true, if a value has been set; false if it has not changed
	 */
	function set( $parname, $parvalue, $make_null = false )
	{
		switch( $parname )
		{
			case 'icq':
				return $this->set_param( $parname, 'number', $parvalue, true );

			case 'level':
			case 'notify':
			case 'showonline':
				return $this->set_param( $parname, 'number', $parvalue, $make_null );

			case 'validated':
				return $this->set_param( $parname, 'number', $parvalue ? 1 : 0, $make_null );	// convert boolean

			case 'ctry_ID':
			default:
				return $this->set_param( $parname, 'string', $parvalue, $make_null );
		}
	}


	/**
	 * Set date created.
	 *
	 * @param integer seconds since Unix Epoch.
	 */
	function set_datecreated( $datecreated, $isYMDhour = false )
	{
		if( !$isYMDhour )
		{
			$datecreated = date('Y-m-d H:i:s', $datecreated );
		}
		// Set value:
		$this->datecreated = $datecreated;
		// Remmeber change for later db update:
		$this->dbchange( 'dateYMDhour', 'string', 'datecreated' );
	}


	/**
	 * Set email address of the user.
	 *
	 * If the email address has changed and we're configured to invalidate the user in this case,
	 * the user's account gets invalidated here.
	 *
	 * @param string email address to set for the User
	 * @return boolean true, if set; false if not changed
	 */
	function set_email( $email )
	{
		global $Settings;

		$r = parent::set_param( 'email', 'string', $email );

		// Change "validated" status to false (if email has changed and Settings are available, which they are not during install):
		if( $r && isset($Settings) && $Settings->get('newusers_revalidate_emailchg') )
		{ // In-validate account, because (changed) email has not been verified yet:
			parent::set_param( 'validated', 'number', 0 );
		}

		return $r;
	}


	/**
	 * Set new Group.
	 *
	 * @param Group the Group object to put the user into
	 * @return boolean true if set, false if not changed
	 */
	function set_Group( & $Group )
	{
		if( $Group !== $this->Group )
		{
			$this->Group = & $Group;

			$this->dbchange( 'user_grp_ID', 'number', 'Group->get(\'ID\')' );

			return true;
		}

		return false;
	}

	/**
	 * @deprecated by {@link User::set_Group()} since 1.9
	 */
	function setGroup( & $Group )
	{
		global $Debuglog;
		$Debuglog->add( 'Call to deprecated method User::setGroup(), use set_Group() instead.', 'deprecated' );
		return $this->set_Group( $Group );
	}


	/**
	 * Get the {@link Group} of the user.
	 *
	 * @return Group (by reference)
	 */
	function & get_Group()
	{
		if( ! isset($this->Group) )
		{
			$GroupCache = & get_GroupCache();
			$this->Group = & $GroupCache->get_by_ID($this->group_ID);
		}
		return $this->Group;
	}


  /**
	 * Check password
	 *
	 * @param string password
	 * @param boolean Is the password parameter already MD5()'ed?
	 * @return boolean
	 */
	function check_password( $pass, $pass_is_md5 = false )
	{
		if( !$pass_is_md5 )
		{
			$pass = md5( $pass );
		}
		// echo 'pass: ', $pass, '/', $this->pass;

		return ( $pass == $this->pass );
	}


	/**
	 * Check permission for this user
	 *
	 * @param string Permission name, can be one of:
	 *                - 'edit_timestamp'
	 *                - 'cats_post_statuses', see {@link User::check_perm_catsusers()}
	 *                - either group permission names, see {@link Group::check_perm()}
	 *                - either blogusers permission names, see {@link User::check_perm_blogusers()}
	 * @param string Permission level
	 * @param boolean Execution will halt if this is !0 and permission is denied
	 * @param mixed Permission target (blog ID, array of cat IDs, Item...)
	 * @return boolean 0 if permission denied
	 */
	function check_perm( $permname, $permlevel = 'any', $assert = false, $perm_target = NULL )
	{
		global $Debuglog;

		if( is_object($perm_target) && isset($perm_target->ID) )
		{
			$perm_target_ID = $perm_target->ID;
		}
		elseif( !is_array($perm_target) )
		{
			$perm_target_ID = $perm_target;
		}

		if( isset($perm_target_ID)	// if it makes sense to check the cache
			&& isset($this->cache_perms[$permname][$permlevel][$perm_target_ID]) )
		{ // Permission in available in Cache:
			$Debuglog->add( "Got perm [$permname][$permlevel][$perm_target_ID] from cache", 'perms' );
			return $this->cache_perms[$permname][$permlevel][$perm_target_ID];
		}

		//$Debuglog->add( "Querying perm [$permname][$permlevel]".( isset( $perm_target_ID ) ? '['.$perm_target_ID.']' : '' ).']', 'perms' );
		//pre_dump( 'Perm target: '.var_export( $perm_target, true ) );

		$perm = false;

		switch( $permname )
		{ // What permission do we want to check?
			case 'cats_post_statuses':
			case 'cats_post!published':
			case 'cats_post!protected':
			case 'cats_post!private':
			case 'cats_post!draft':
			case 'cats_post!deprecated':
			case 'cats_post!redirected':
			case 'cats_page':
			case 'cats_intro':
			case 'cats_podcast':
			case 'cats_sidebar':
				// Category permissions...
				if( ! is_array( $perm_target ) )
				{	// We need an array here:
					$perm_target = array( $perm_target );
				}

				// First we need to create an array of blogs, not cats
				$perm_target_blogs = array();
				foreach( $perm_target as $loop_cat_ID )
				{
					$loop_cat_blog_ID = get_catblog( $loop_cat_ID );
					// echo "cat $loop_cat_ID -> blog $loop_cat_blog_ID <br />";
					if( ! in_array( $loop_cat_blog_ID, $perm_target_blogs ) )
					{ // not already in list: add it:
						$perm_target_blogs[] = $loop_cat_blog_ID;
					}
				}

				$perm = true; // Permission granted if no blog denies it below
				$blogperm = 'blog_'.substr( $permname, 5 );
				// Now we'll check permissions for each blog:
				foreach( $perm_target_blogs as $loop_blog_ID )
				{
					if( ! $this->check_perm( $blogperm, $permlevel, false, $loop_blog_ID ) )
					{ // If at least one blog denies the permission:
						$perm = false;
						break;
					}
				}
				break;

			case 'blog_ismember':
			case 'blog_post_statuses':
			case 'blog_post!published':
			case 'blog_post!protected':
			case 'blog_post!private':
			case 'blog_post!draft':
			case 'blog_post!deprecated':
			case 'blog_post!redirected':
			case 'blog_del_post':
			case 'blog_comments':
			case 'blog_draft_comments':
			case 'blog_published_comments':
			case 'blog_deprecated_comments':
			case 'blog_properties':
			case 'blog_cats':
			case 'blog_genstatic':
			case 'blog_page':
			case 'blog_intro':
			case 'blog_podcast':
			case 'blog_sidebar':
				// Blog permission to edit its properties...
				if( $this->check_perm_blogowner( $perm_target_ID ) )
				{	// Owner can do *almost* anything:
					$perm = true;
					break;
				}
				/* continue */
			case 'blog_admin': // This is what the owner does not have access to!

				// Group may grant VIEW access, FULL access:
				$this->get_Group();
				if( $this->Group->check_perm( 'blogs', $permlevel ) )
				{ // If group grants a global permission:
					$perm = true;
					break;
				}

				if( $perm_target > 0 )
				{ // Check user perm for this blog:
					$perm = $this->check_perm_blogusers( $permname, $permlevel, $perm_target_ID );
					if( ! $perm )
					{ // Check groups for permissions to this specific blog:
						$perm = $this->Group->check_perm_bloggroups( $permname, $permlevel, $perm_target_ID );
					}
				}
				break;

			case 'item_post!CURSTATUS':
				/**
				 * @var Item
				 */
				$Item = & $perm_target;
				// Change the permname to one of the following:
				$permname = 'item_post!'.$Item->status;
			case 'item_post!published':
			case 'item_post!protected':
			case 'item_post!private':
			case 'item_post!draft':
			case 'item_post!deprecated':
			case 'item_post!redirected':
				// Get the Blog ID
				/**
				 * @var Item
				 */
				$Item = & $perm_target;
				$blog_ID = $Item->get_blog_ID();

				if( $this->check_perm_blogowner( $blog_ID ) )
				{	// Owner can do *almost* anything:
					$perm = true;
					break;
				}

				// Group may grant VIEW access, FULL access:
				$this->get_Group();
				if( $this->Group->check_perm( 'blogs', $permlevel ) )
				{ // If group grants a global permission:
					$perm = true;
					break;
				}

				// Check permissions at the blog level:
				$blog_permname = 'blog_'.substr( $permname, 5 );
				$perm = $this->check_perm_blogusers( $blog_permname, $permlevel, $blog_ID, $Item );
				if( ! $perm )
				{ // Check groups for permissions to this specific blog:
					$perm = $this->Group->check_perm_bloggroups( $blog_permname, $permlevel, $blog_ID, $Item, $this );
				}
				break;

			case 'stats':
				// Blog permission to edit its properties...
				$this->get_Group();

				// Group may grant VIEW acces, FULL access:
				if( $this->Group->check_perm( $permname, $permlevel ) )
				{ // If group grants a global permission:
					$perm = true;
					break;
				}

				if( $perm_target > 0 )
				{ // Check user perm for this blog:
					$perm = $this->check_perm_blogusers( $permname, $permlevel, $perm_target );
					if ( ! $perm )
					{ // Check groups for permissions to this specific blog:
						$perm = $this->Group->check_perm_bloggroups( $permname, $permlevel, $perm_target );
					}
				}
				break;

			case 'edit_timestamp':
				// Global permission to edit timestamps...
				// fp> TODO: merge below
				$perm = ($this->level >= 5);
				break;

			case 'files':
				$this->get_Group();
				$perm = $this->Group->check_perm( $permname, $permlevel );

				/* Notes:
				 *  - $perm_target can be:
				 *    - NULL or 0: check global group permission only
				 *    - positive: check global group permission and
				 *      (if granted) if a specific blog denies it.
* fp> This is BAD BAD BAD because it's inconsistent with the other permissions
* in b2evolution. There should NEVER be a denying. ony additional allowing.
* It's also inconsistent with most other permission systems.
* The lower file permission level for groups is now called "No Access"
* This should be renamed to "Depending on each blog's permissions"
* Whatever general permissions you have on files, blog can give you additional permissions
* but they can never take a global perm away.
* Tblue> On the permissions page it says that the blog perms will be restricted
* by any global perms, which means to me that a blog cannot grant e. g.
* the files upload perm if this perm isn't granted globally... But apparently
* it shouldn't be like that?! I understand it should be like that then:
* if( ! $perm && $perm_target && in_array( $permlevel, array( 'add', 'view', 'edit' ) )
* {
* 		// check if blog grants permission.
* }
* If this is correct, we should remove the note on the blog permissions
* pages and the group properties form.
* fp> ok, I had forgotten we had that old message, but still it doesn't say it will, it says it *may* !
* To be exact the message should be "
* Note: General group permissions may further restrict or extend any permissions defined here."
* Restriction should only happen when "NO ACCESS" is selected
* But when "Depending on each blog's permissions" is selected, THEN (and I guess ONLY then) the blog permissions should be used
* Note: This is quite messy actually. maybe it would make more sense to separate group permissions by "root type":
* i-e nto use the same permission for blog roots vs user root vs shared root vs skins root
* what do you think?
* Tblue> That sounds OK. So we would add another option to the global
* 'files' group perm setting ("Depending on each blog's permissions"), right?
* fp> yes.
* tb> Regarding separation: It could make sense. The blog-specific permissions would only
* affect blog roots (and if "Depending on each blog's permissions" is selected;
* for the other roots we would add separate (global) settings...
* fp> yes.
				 *  - Only a $permlevel of 'add', 'view' or 'edit' can be
				 *    denied by blog permissions.
				 *  - If the group grants the 'all' permission, blogs cannot
				 *    deny it.
				 */
/*
				if( $perm && $perm_target && in_array( $permlevel, array( 'add', 'view', 'edit' ) )
					&& $this->Group->get( 'perm_files' ) != 'all' )
				{	// Check specific blog perms:
					$perm = $this->check_perm_blogusers( $permname, $permlevel, $perm_target );
					if ( ! $perm )
					{ // Check groups for permissions for this specific blog:
						$perm = $this->Group->check_perm_bloggroups( $permname, $permlevel, $perm_target );
					}
				}
*/
				break;

			default:
				// Check pluggable permissions using user permission check function
				$perm = Module::check_perm( $permname, $permlevel, $perm_target, 'user_func' );
				if( $perm === true || $perm === NULL )
				{	// We can check group permissions

					// Other global permissions (see if the group can handle them).
					// Forward request to group:
					$this->get_Group();
					$perm = $this->Group->check_perm( $permname, $permlevel, $perm_target );
				}
		}

		// echo "<br>Checking user perm $permname:$permlevel:$perm_target";
		$Debuglog->add( "User perm $permname:$permlevel:"
			.( is_object($perm_target) ? get_class($perm_target).'('.$perm_target_ID.')' : $perm_target ) // prevent catchable E_FATAL with PHP 5.2 (because there's no __tostring for e.g. Item)
			.' => '.($perm?'granted':'DENIED'), 'perms' );

		if( ! $perm && $assert )
		{ // We can't let this go on!
			global $app_name;
			debug_die( sprintf( /* %s is the application name, usually "b2evolution" */ T_('Group/user permission denied by %s!'), $app_name )." ($permname:$permlevel:".( is_object( $perm_target ) ? get_class( $perm_target ).'('.$perm_target_ID.')' : $perm_target ).")" );
		}

		if( isset($perm_target_ID) )
		{
			// echo "cache_perms[$permname][$permlevel][$perm_target] = $perm;";
			$this->cache_perms[$permname][$permlevel][$perm_target_ID] = $perm;
		}

		return $perm;
	}


	/**
	 * Check if the user is the owner of the designated blog (which gives him a lot of permissions)
	 *
	 * @param integer
	 * @return boolean
	 */
	function check_perm_blogowner( $blog_ID )
	{
		if( empty($blog_ID) )
		{
			return false;
		}

		$BlogCache = & get_BlogCache();
    /**
		 * @var Blog
		 */
		$Blog = & $BlogCache->get_by_ID( $blog_ID );

		return ( $Blog->owner_user_ID == $this->ID );
	}


	/**
	 * Check permission for this user on a specified blog
	 *
	 * This is not for direct use, please call {@link User::check_perm()} instead
	 *
	 * @see User::check_perm()
	 * @param string Permission name, can be one of the following:
	 *                  - blog_ismember
	 *                  - blog_post_statuses
	 *                  - blog_del_post
	 *                  - blog_comments
	 *                  - blog_cats
	 *                  - blog_properties
	 *                  - blog_genstatic
	 * @param string Permission level
	 * @param integer Permission target blog ID
	 * @param Item Item that we want to edit
	 * @return boolean 0 if permission denied
	 */
	function check_perm_blogusers( $permname, $permlevel, $perm_target_blog, $Item = NULL )
	{
		global $DB;
		// echo "checkin for $permname >= $permlevel on blog $perm_target_blog<br />";

		$BlogCache = & get_BlogCache();
		/**
		 * @var Blog
		 */
		$Blog = & $BlogCache->get_by_ID( $perm_target_blog );
		if( ! $Blog->advanced_perms )
		{	// We do not abide to advanced perms
			return false;
		}

		if( ! isset( $this->blog_post_statuses[$perm_target_blog] ) )
		{ // Allowed blog post statuses have not been loaded yet:
			if( $this->ID == 0 )
			{ // User not in DB, nothing to load!:
				return false;	// Permission denied
			}

			// Load now:
			// echo 'loading allowed statuses';
			$query = "
				SELECT *
				  FROM T_coll_user_perms
				 WHERE bloguser_blog_ID = $perm_target_blog
				   AND bloguser_user_ID = $this->ID";
			$row = $DB->get_row( $query, ARRAY_A );

			if( empty($row) )
			{ // No rights set for this Blog/User: remember this (in order not to have the same query next time)
				$this->blog_post_statuses[$perm_target_blog] = array(
						'blog_ismember' => '0',
						'blog_post_statuses' => array(),
						'blog_edit' => 'no',
						'blog_del_post' => '0',
						'blog_comments' => '0',
						'blog_draft_comments' => '0',
						'blog_published_comments' => '0',
						'blog_deprecated_comments' => '0',
						'blog_cats' => '0',
						'blog_properties' => '0',
						'blog_admin' => '0',
						'blog_page' => '0',
						'blog_intro' => '0',
						'blog_podcast' => '0',
						'blog_sidebar' => '0',
						'blog_media_upload' => '0',
						'blog_media_browse' => '0',
						'blog_media_change' => '0',
					);
			}
			else
			{ // OK, rights found:
				$this->blog_post_statuses[$perm_target_blog] = array();

				$this->blog_post_statuses[$perm_target_blog]['blog_ismember'] = $row['bloguser_ismember'];

				$bloguser_perm_post = $row['bloguser_perm_poststatuses'];
				if( empty($bloguser_perm_post ) )
					$this->blog_post_statuses[$perm_target_blog]['blog_post_statuses'] = array();
				else
					$this->blog_post_statuses[$perm_target_blog]['blog_post_statuses'] = explode( ',', $bloguser_perm_post );

				$this->blog_post_statuses[$perm_target_blog]['blog_edit'] = $row['bloguser_perm_edit'];
				$this->blog_post_statuses[$perm_target_blog]['blog_del_post'] = $row['bloguser_perm_delpost'];
				$this->blog_post_statuses[$perm_target_blog]['blog_comments'] = $row['bloguser_perm_publ_cmts']
					+ $row['bloguser_perm_draft_cmts'] +  $row['bloguser_perm_depr_cmts'];
				$this->blog_post_statuses[$perm_target_blog]['blog_draft_comments'] = $row['bloguser_perm_draft_cmts'];
				$this->blog_post_statuses[$perm_target_blog]['blog_published_comments'] = $row['bloguser_perm_publ_cmts'];
				$this->blog_post_statuses[$perm_target_blog]['blog_deprecated_comments'] = $row['bloguser_perm_depr_cmts'];
				$this->blog_post_statuses[$perm_target_blog]['blog_cats'] = $row['bloguser_perm_cats'];
				$this->blog_post_statuses[$perm_target_blog]['blog_properties'] = $row['bloguser_perm_properties'];
				$this->blog_post_statuses[$perm_target_blog]['blog_admin'] = $row['bloguser_perm_admin'];
				$this->blog_post_statuses[$perm_target_blog]['blog_page'] = $row['bloguser_perm_page'];
				$this->blog_post_statuses[$perm_target_blog]['blog_intro'] = $row['bloguser_perm_intro'];
				$this->blog_post_statuses[$perm_target_blog]['blog_podcast'] = $row['bloguser_perm_podcast'];
				$this->blog_post_statuses[$perm_target_blog]['blog_sidebar'] = $row['bloguser_perm_sidebar'];
				$this->blog_post_statuses[$perm_target_blog]['blog_media_upload'] = $row['bloguser_perm_media_upload'];
				$this->blog_post_statuses[$perm_target_blog]['blog_media_browse'] = $row['bloguser_perm_media_browse'];
				$this->blog_post_statuses[$perm_target_blog]['blog_media_change'] = $row['bloguser_perm_media_change'];
			}
		}

		// Check if permission is granted:
		switch( $permname )
		{
			case 'stats':
				// Wiewing stats is the same perm as being authorized to edit properties: (TODO...)
				if( $permlevel == 'view' )
				{
					return $this->blog_post_statuses[$perm_target_blog]['blog_properties'];
				}
				// No other perm can be granted here (TODO...)
				return false;

			case 'blog_genstatic':
			case 'blog_post_statuses':
				// echo count($this->blog_post_statuses);
				return ( count($this->blog_post_statuses[$perm_target_blog]['blog_post_statuses']) > 0 );

			case 'blog_post!published':
			case 'blog_post!protected':
			case 'blog_post!private':
			case 'blog_post!draft':
			case 'blog_post!deprecated':
			case 'blog_post!redirected':
				// We want a specific permission:
				$subperm = substr( $permname, 10 );
				// echo "checking : $subperm - ", implode( ',', $this->blog_post_statuses[$perm_target_blog]['blog_post_statuses']  ), '<br />';
				$perm = in_array( $subperm, $this->blog_post_statuses[$perm_target_blog]['blog_post_statuses'] );

				// TODO: the following probably should be handled by the Item class!
				if( $perm && $permlevel == 'edit' && !empty($Item) )
				{	// Can we edit this specific Item?
					switch( $this->blog_post_statuses[$perm_target_blog]['blog_edit'] )
					{
						case 'own':
							// Own posts only:
							return ($Item->creator_user_ID == $this->ID);

						case 'lt':
							// Own + Lower level posts only:
							if( $Item->creator_user_ID == $this->ID )
							{
								return true;
							}
							$item_creator_User = & $Item->get_creator_User();
							return ( $item_creator_User->level < $this->level );

						case 'le':
							// Own + Lower or equal level posts only:
							if( $Item->creator_user_ID == $this->ID )
							{
								return true;
							}
							$item_creator_User = & $Item->get_creator_User();
							return ( $item_creator_User->level <= $this->level );

						case 'all':
							return true;

						case 'no':
						default:
							return false;
					}
				}

				return $perm;

			case 'files':
				switch( $permlevel )
				{
					case 'add':
						return $this->blog_post_statuses[$perm_target_blog]['blog_media_upload'];
					case 'view':
						return $this->blog_post_statuses[$perm_target_blog]['blog_media_browse'];
					case 'edit':
						return $this->blog_post_statuses[$perm_target_blog]['blog_media_change'];
					default:
						return false;
				}
				break;

			default:
				// echo $permname, '=', $this->blog_post_statuses[$perm_target_blog][$permname], ' ';
				return $this->blog_post_statuses[$perm_target_blog][$permname];
		}
	}


	/**
	 * Check if this user and his group accept receiving private messages or not
	 *
	 * @return boolean
	 */
	function accepts_pm()
	{
		if( $this->allow_msgform % 2 == 1 )
		{
			$Group = & $this->get_Group();
			return $Group->check_messaging_perm();
		}
		return false;
	}


	/**
	 * Check if this user accepts receiving emails and has an email address
	 *
	 * @return boolean
	 */
	function accepts_email()
	{
		return ( $this->allow_msgform > 1 ) && ( ! empty( $this->email ) );
	}


	/**
	 * Get messaging possibilities between current user and this user
	 *
	 * @return NULL|string allowed messaging possibility: PM > email > login > NULL
	 */
	function get_msgform_possibility( $current_User = NULL )
	{
		if( is_logged_in() )
		{ // current User is a registered user
			if( $current_User == NULL )
			{
				global $current_User;
			}
			if( $this->accepts_pm() && $current_User->accepts_pm() && ( $this->ID != $current_User->ID ) )
			{ // both user has permission to send or receive private message and not the same user
				return 'PM';
			}
			if( $this->accepts_email() )
			{ // this user allows email => send email
				return 'email';
			}
		}
		else
		{ // current User is not logged in
			if( $this->accepts_email() )
			{ // this user allows email
				return 'email';
			}
			if( $this->accepts_pm() )
			{ // no email option try to log in and send private message (just registered users can send PM)
				return 'login';
			}
		}
		// no messaging option between current_User and this user
		return NULL;
	}


	/**
	 * Insert object into DB based on previously recorded changes
	 *
	 * Triggers the plugin event AfterUserInsert.
	 *
	 * @return boolean true on success
	 */
	function dbinsert()
	{
		global $Plugins, $DB;

		$DB->begin();

		if( $result = parent::dbinsert() )
		{ // We could insert the user object..

			// Add new fields:
			if( !empty($this->new_fields) )
			{
				$sql = 'INSERT INTO T_users__fields( uf_user_ID, uf_ufdf_ID, uf_varchar )
								VALUES ('.$this->ID.', '.implode( '), ('.$this->ID.', ', $this->new_fields ).' )';
				$DB->query( $sql, 'Insert new fields' );
			}

			// Notify plugins:
			// A user could be created also in another DB (to synchronize it with b2evo)
			$Plugins->trigger_event( 'AfterUserInsert', $params = array( 'User' => & $this ) );

			$Group = & $this->get_Group();
			if( $Group->check_perm( 'perm_getblog', 'allowed' ) )
			{ // automatically create new blog for this user
				// TODO: dh> also set locale, or use it at least for urltitle_validate below. From the User (new blog owner)?
				$new_Blog = new Blog( NULL );
				$shortname = $this->get( 'login' );
				$new_Blog->set( 'owner_user_ID', $this->ID );
				$new_Blog->set( 'shortname', $shortname );
				$new_Blog->set( 'name', $shortname.'\'s blog' );
				$new_Blog->set( 'locale', $this->get( 'locale' ));
				$new_Blog->set( 'urlname', urltitle_validate( $shortname, $shortname, $new_Blog->ID, false, 'blog_urlname', 'blog_ID', 'T_blogs', $this->get( 'locale' ) ) );
				$new_Blog->create();
			}
		}

		$DB->commit();

		return $result;
	}


	/**
	 * Update the DB based on previously recorded changes.
	 *
	 * Triggers the plugin event AfterUserUpdate.
	 */
	function dbupdate()
	{
		global $DB, $Plugins;

		$DB->begin();

		parent::dbupdate();

		// Update existing fields:
		if( !empty($this->updated_fields) )
		{
			foreach( $this->updated_fields as $uf_ID=>$uf_val )
			{
				if( empty( $uf_val ) )
				{	// Delete field:
					$DB->query( 'DELETE FROM T_users__fields
														 WHERE uf_ID = '.$uf_ID );
				}
				else
				{	// Update field:
					$DB->query( 'UPDATE T_users__fields
													SET uf_varchar = '.$DB->quote($uf_val).'
												WHERE uf_ID = '.$uf_ID );
				}
			}
		}

		// Add new fields:
		if( !empty($this->new_fields) )
		{
			$sql = 'INSERT INTO T_users__fields( uf_user_ID, uf_ufdf_ID, uf_varchar )
							VALUES ('.$this->ID.', '.implode( '), ('.$this->ID.', ', $this->new_fields ).' )';
			$DB->query( $sql, 'Insert new fields' );
		}

		// Notify plugins:
		// Example: An authentication plugin could synchronize/update the password of the user.
		$Plugins->trigger_event( 'AfterUserUpdate', $params = array( 'User' => & $this ) );

		$DB->commit();

		// This User has been modified, cached content depending on it should be invalidated:
		BlockCache::invalidate_key( 'user_ID', $this->ID );

		return true;
	}


	/**
	 * Delete user and dependencies from database
	 *
	 * Includes WAY TOO MANY requests because we try to be compatible with MySQL 3.23, bleh!
	 *
	 * @param Log Log object where output gets added (by reference).
	 */
	function dbdelete( & $Log )
	{
		global $DB, $Plugins;

		if( $this->ID == 0 ) debug_die( 'Non persistant object cannot be deleted!' );

		$DB->begin();

		// Transform registered user comments to unregistered:
		$ret = $DB->query( 'UPDATE T_comments
												SET comment_author_ID = NULL,
														comment_author = '.$DB->quote( $this->get('preferredname') ).',
														comment_author_email = '.$DB->quote( $this->get('email') ).',
														comment_author_url = '.$DB->quote( $this->get('url') ).'
												WHERE comment_author_ID = '.$this->ID );
		if( is_a( $Log, 'log' ) )
		{
			$Log->add( 'Transforming user\'s comments to unregistered comments... '.sprintf( '(%d rows)', $ret ), 'note' );
		}

		// Get list of posts that are going to be deleted (3.23)
		$post_list = implode( ',', $DB->get_col( '
				SELECT post_ID
				  FROM T_items__item
				 WHERE post_creator_user_ID = '.$this->ID ) );

		if( !empty( $post_list ) )
		{
			// Delete comments
			$ret = $DB->query( "DELETE FROM T_comments
													WHERE comment_post_ID IN ($post_list)" );
			if( is_a( $Log, 'log' ) )
			{
				$Log->add( sprintf( 'Deleted %d comments on user\'s posts.', $ret ), 'note' );
			}

			// Delete post extracats
			$ret = $DB->query( "DELETE FROM T_postcats
													WHERE postcat_post_ID IN ($post_list)" );
			if( is_a( $Log, 'log' ) )
			{
				$Log->add( sprintf( 'Deleted %d extracats of user\'s posts\'.', $ret ) ); // TODO: geeky wording.
			}

			// Posts will we auto-deleted by parent method
		}
		else
		{ // no posts
			if( is_a( $Log, 'log' ) )
			{
				$Log->add( 'No posts to delete.', 'note' );
			}
		}

		// remember ID, because parent method resets it to 0
		$old_ID = $this->ID;

		// Delete main object:
		if( ! parent::dbdelete() )
		{
			$DB->rollback();

			$Log->add( 'User has not been deleted.', 'error' );
			return false;
		}

		$DB->commit();

		if( is_a( $Log, 'log' ) )
		{
			$Log->add( 'Deleted User.', 'note' );
		}

		// Notify plugins:
		$this->ID = $old_ID;
		$Plugins->trigger_event( 'AfterUserDelete', $params = array( 'User' => & $this ) );
		$this->ID = 0;

		return true;
	}


	function callback_optionsForIdMode( $value )
	{
		$field_options = '';
		$idmode = $this->get( 'idmode' );

		foreach( array( 'nickname' => array( T_('Nickname') ),
										'login' => array( T_('Login') ),
										'firstname' => array( T_('First name') ),
										'lastname' => array( T_('Last name') ),
										'namefl' => array( T_('First name').' '.T_('Last name'),
																				implode( ' ', array( $this->get('firstname'), $this->get('lastname') ) ) ),
										'namelf' => array( T_('Last name').' '.T_('First name'),
																				implode( ' ', array( $this->get('lastname'), $this->get('firstname') ) ) ),
										)
							as $lIdMode => $lInfo )
		{
			$disp = isset( $lInfo[1] ) ? $lInfo[1] : $this->get($lIdMode);

			$field_options .= '<option value="'.$lIdMode.'"';
			if( $value == $lIdMode )
			{
				$field_options .= ' selected="selected"';
			}
			$field_options .= '>'.( !empty( $disp ) ? $disp.' ' : ' - ' )
												.'&laquo;'.$lInfo[0].'&raquo;'
												.'</option>';
		}

		return $field_options;
	}


	/**
	 * Send an email to the user with a link to validate/confirm his email address.
	 *
	 * If the email could get sent, it saves the used "request_id" into the user's Session.
	 *
	 * @param string URL, where to redirect the user after he clicked the validation link (gets saved in Session).
	 * @return boolean True, if the email could get sent; false if not
	 */
	function send_validate_email( $redirect_to_after = NULL )
	{
		global $app_name, $htsrv_url_sensitive, $Session;

		$request_id = generate_random_key(22);

		$message = T_('You need to validate your email address by clicking on the following link.')
			."\n\n"
			.T_('Login:')." $this->login\n"
			.sprintf( /* TRANS: %s gets replaced by $app_name (normally "b2evolution") */ T_('Link to validate your %s account:'), $app_name )
			."\n"
			.$htsrv_url_sensitive.'login.php?action=validatemail'
				.'&reqID='.$request_id
				.'&sessID='.$Session->ID  // used to detect cookie problems
			."\n\n"
			.T_('Please note:')
			.' '.T_('For security reasons the link is only valid for your current session (by means of your session cookie).');

		$r = send_mail( $this->email, NULL, sprintf( T_('Validate your email address for "%s"'), $this->login ), $message );

		if( $r )
		{ // save request_id into Session
			$request_ids = $Session->get( 'core.validatemail.request_ids' );
			if( ! is_array($request_ids) )
			{
				$request_ids = array();
			}
			$request_ids[] = $request_id;
			$Session->set( 'core.validatemail.request_ids', $request_ids, 86400 * 2 ); // expires in two days (or when clicked)
			if( isset($redirect_to_after) )
			{
				$Session->set( 'core.validatemail.redirect_to', $redirect_to_after  );
			}
			$Session->dbsave(); // save immediately
		}

		return $r;
	}


	// Template functions {{{

	/**
	 * Template function: display user's level
	 */
	function level()
	{
		$this->disp( 'level', 'raw' );
	}


	/**
	 * Template function: display user's login
	 *
	 * @param string Output format, see {@link format_to_output()}
	 */
	function login( $format = 'htmlbody' )
	{
		$this->disp( 'login', $format );
	}


	/**
	 * Template helper function: Get a link to a message form for this user.
	 *
	 * @param string url of the message form
	 * @param string to display before link
	 * @param string to display after link
	 * @param string link text
	 * @param string link title
	 * @param string class name
	 */
	function get_msgform_link( $form_url = NULL, $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '' )
	{
		if( empty($this->email) )
		{ // We have no email for this User :(
			return false;
		}
		if( ! $this->get_msgform_possibility() )
		{	// There is no way this user accepts receiving messages.
			return false;
		}

		if( is_null($form_url) )
		{
			global $Blog;
			$form_url = isset($Blog) ? $Blog->get('msgformurl') : '';
		}

		$form_url = url_add_param( $form_url, 'recipient_id='.$this->ID.'&amp;redirect_to='.rawurlencode(url_rel_to_same_host(regenerate_url('','','','&'), $form_url)) );

		if( $title == '#' ) $title = T_('Send email to user');
		if( $text == '#' ) $text = get_icon( 'email', 'imgtag', array( 'class' => 'middle', 'title' => $title ) );

		$r = '';
		$r .= $before;
		$r .= '<a href="'.$form_url.'" title="'.$title.'"';
		if( !empty( $class ) )
		{
			$r .= ' class="'.$class.'"';
		}
		$r .= '>'.$text.'</a>';
		$r .= $after;

		return $r;
	}


	/**
	 * Template function: display a link to a message form for this user
	 *
	 * @param string url of the message form
	 * @param string to display before link
	 * @param string to display after link
	 * @param string link text
	 * @param string link title
	 * @param string class name
	 */
	function msgform_link( $form_url = NULL, $before = ' ', $after = ' ', $text = '#', $title = '#', $class = '' )
	{
		echo $this->get_msgform_link( $form_url, $before, $after, $text, $title, $class );
	}


	/**
	 * Template function: display user's preferred name
	 *
	 * @param string Output format, see {@link format_to_output()}
	 */
	function preferred_name( $format = 'htmlbody' )
	{
		echo format_to_output( $this->get_preferred_name(), $format );
	}


	/**
	 * Template function: display user's URL
	 *
	 * @param string string to display before the date (if changed)
	 * @param string string to display after the date (if changed)
	 * @param string Output format, see {@link format_to_output()}
	 */
	function url( $before = '', $after = '', $format = 'htmlbody' )
	{
		if( !empty( $this->url ) )
		{
			echo $before;
			$this->disp( 'url', $format );
			echo $after;
		}
	}


	/**
	 * Template function: display number of user's posts
	 */
	function num_posts( $format = 'htmlbody' )
	{
		echo format_to_output( $this->get_num_posts(), $format );
	}


	/**
	 * Template function: display first name of the user
	 */
	function first_name( $format = 'htmlbody' )
	{
		$this->disp( 'firstname', $format );
	}


	/**
	 * Template function: display last name of the user
	 */
	function last_name( $format = 'htmlbody' )
	{
		$this->disp( 'lastname', $format );
	}


	/**
	 * Template function: display nickname of the user
	 */
	function nick_name( $format = 'htmlbody' )
	{
		$this->disp( 'nickname', $format );
	}


	/**
	 * Template function: display email of the user
	 */
	function email( $format = 'htmlbody' )
	{
		$this->disp( 'email', $format );
	}


	/**
	 * Template function: display ICQ of the user
	 */
	function icq( $format = 'htmlbody' )
	{
		$this->disp( 'icq', $format );
	}


	/**
	 * Template function: display AIM of the user.
	 *
	 * NOTE: Replaces spaces with '+' ?!?
	 */
	function aim( $format = 'htmlbody' )
	{
		echo format_to_output( str_replace(' ', '+', $this->get('aim') ), $format );
	}


	/**
	 * Template function: display Yahoo IM of the user
	 */
	function yim( $format = 'htmlbody' )
	{
		$this->disp( 'yim', $format );
	}


	/**
	 * Template function: display MSN of the user
	 */
	function msn( $format = 'htmlbody' )
	{
		$this->disp( 'msn', $format );
	}

	// }}}


	function has_avatar()
	{
		global $Settings;

		return ( !empty( $this->avatar_file_ID ) && $Settings->get('allow_avatars') );
	}


	/**
	 * Get {@link File} object of the user's avatar.
	 *
	 * @return File This may be NULL.
	 */
	function & get_avatar_File()
	{
		$File = NULL;

		if( $this->has_avatar() )
		{
			$FileCache = & get_FileCache();

			// Do not halt on error. A file can disappear without the profile being updated.
			/**
			 * @var File
			 */
			$File = & $FileCache->get_by_ID( $this->avatar_file_ID, false, false );
		}

		return $File;
	}


	/**
	 * Get avatar IMG tag.
	 *
	 * @return string
	 */
	function get_avatar_imgtag( $size = 'crop-64x64', $class = 'avatar', $align = '' )
	{
		/**
		 * @var File
		 */
		if( ! $File = & $this->get_avatar_File() )
		{
			return '';
		}
		$r = $File->get_thumb_imgtag( $size, $class, $align );

		return $r;
	}


	/**
	 * Add a user field
	 */
	function userfield_add( $type, $val )
	{
		global $DB;
		$this->new_fields[] = $type.', '.$DB->quote($val);
	}


	/**
	 * Update an user field. Empty fields will be deleted on dbupdate.
	 */
	function userfield_update( $uf_ID, $val )
	{
		global $DB;
		$this->updated_fields[$uf_ID] = $val;
		// pre_dump( $uf_ID, $val);
	}


	/**
	 * Load userfields
	 */
	function userfields_load()
	{
		global $DB;

		$userfields = $DB->get_results( '
			SELECT uf_ID, uf_ufdf_ID, uf_varchar
				FROM T_users__fields
			 WHERE uf_user_ID = '.$this->ID );

		foreach( $userfields as $userfield )
		{
			// Save all data for this field:
			$this->userfields[$userfield->uf_ID] = array( $userfield->uf_ufdf_ID, $userfield->uf_varchar);
			// Save index
			$this->userfields_by_type[$userfield->uf_ufdf_ID][] = $userfield->uf_ID;
		}

		// Also make sure the definitions are loaded
		$this->userfield_defs_load();
	}


	/**
	 * Load userfields defs
	 */
	function userfield_defs_load()
	{
		global $DB;

		if( !isset($this->userfield_defs) )
		{
			$userfield_defs = $DB->get_results( '
				SELECT ufdf_ID, ufdf_type, ufdf_name
					FROM T_users__fielddefs' );

			foreach( $userfield_defs as $userfield_def )
			{
				$this->userfield_defs[$userfield_def->ufdf_ID] = array( $userfield_def->ufdf_type, $userfield_def->ufdf_name );
			}
		}
	}

	/**
	* Get first field for a specific type
	*
	* @return string or NULL
	*/
	function userfieldget_first_for_type( $type_ID )
	{
		if( !isset($this->userfields_by_type[$type_ID]) )
		{
			return NULL;
		}

		$idx = $this->userfields_by_type[$type_ID][0];

		return $this->userfields[$idx][1];
	}
}

/*
 * $Log: _user.class.php,v $
 */
?>
