Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/users/model/_user.class.php
<?php
/**
 * This file implements the User class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 */
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 $postcode;
   
var $age_min;
    var
$age_max;
    var
$login;
    var
$pass;
    var
$salt;
    var
$pass_driver;
    var
$firstname;
    var
$lastname;
    var
$nickname;
    var
$locale;
    var
$email;
    var
$url;
    var
$datecreated;
    var
$lastseen_ts;
    var
$level;
    var
$avatar_file_ID;
    var
$reg_ctry_ID;
    var
$ctry_ID;
    var
$rgn_ID;
    var
$subrg_ID;
    var
$city_ID;
    var
$source;
    var
$unsubscribe_key;
    var
$gender;
    var
$email_dom_ID;
    var
$profileupdate_date;
    var
$birthday_year;
    var
$birthday_month;
    var
$birthday_day;

   
/**
     * User account status
     *
     * 'new', 'activated', 'manualactivated', 'autoactivated', 'emailchanged', 'deactivated', 'failedactivation', 'pendingdelete', 'closed'
     *
     * @var string
     */
   
var $status;
   
/**
     * User previous status. It will be set only if the user status was changed.
     * @var string
     */
   
var $previous_status;

   
/**
     * Number of posts by this user. Use get_num_posts() to access this (lazy filled).
     * @var array ( key = status name, value = number of posts with status, key = '' - total number with all statuses )
     * @access protected
     */
   
var $_num_posts;

   
/**
     * Number of comments by this user. Use get_num_comments() to access this (lazy filled).
     * @var array  ( key = status name, value = number of comments with status, key = '' - total number with all statuses )
     * @access protected
     */
   
var $_num_comments;

   
/**
     * Number of files by this user. Use get_num_files() to access this (lazy filled).
     * @var array  ( key = type, value = number of files with type )
     * @access protected
     */
   
var $_num_files;

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

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

   
/**
     * Array of the references to groups
     * @see User::get_secondary_groups()
     * @var secondary_groups
     * @access protected
     */
   
var $secondary_groups;

   
/**
     * Country lazy filled
     *
     * @var country
     */
   
var $Country;
    var
$Region;
    var
$Subregion;
    var
$City;

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

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


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

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

   
/**
     * The previous values of significant fields which were changed and these changes should be notified.
     */
   
var $significant_changed_values = array();

   
/**
     * TRUE if user is owner of at least one collection
     * @access protected
     * @var boolean
     */
   
var $is_collection_owner;

   
/**
     * Password driver
     * @var object
     */
   
var $PasswordDriver;

   
/**
     * IDs of newsletters which this user is subscribed to
     * @var array
     */
   
var $newsletter_subscriptions;

   
/**
     * TRUE if user's newsletter subscriptions were updated during user updating
     * @var boolean
     */
   
var $newsletter_subscriptions_updated = false;

   
/**
     * Array of newsletter subscription changes with the following elements:
     *   - string, 'subscribe' | 'unsubscribe'
     *   - array of newsletter IDs
     *   - array of email template params
     * @var array
     */
   
var $newsletter_subscription_changed_values = array();

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

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

        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_password( '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 );
               
$this->set( 'profileupdate_date', date( 'Y-m-d H:i:s', $localtimenow ) );
            }
            else
            {
// We don't know local time here!
               
$this->set_datecreated( time() );
               
$this->set( 'profileupdate_date', date( 'Y-m-d H:i:s' ) );
            }

            if( isset(
$Settings ) )
            {
// Set Group for this user:
               
$GroupCache = & get_GroupCache();
               
$new_user_Group = & $GroupCache->get_by_ID( $Settings->get( 'newusers_grp_ID' ) );
               
$this->set_Group( $new_user_Group );
               
// set status for this user
               
$this->set( 'status', $Settings->get('newusers_mustvalidate') ? 'new' : 'autoactivated' );
            }
            else
            {
               
// set status for this user
               
$this->set( 'status', 'new' );
            }

           
$this->set( 'unsubscribe_key', generate_random_key() );
        }
        else
        {
           
// echo 'Instanciating existing user';
           
$this->ID = $db_row->user_ID;
           
$this->age_min = $db_row->user_age_min;
           
$this->age_max = $db_row->user_age_max;
           
$this->login = $db_row->user_login;
           
$this->pass = $db_row->user_pass;
           
$this->salt = $db_row->user_salt;
           
$this->pass_driver = $db_row->user_pass_driver;
           
$this->firstname = $db_row->user_firstname;
           
$this->lastname = $db_row->user_lastname;
           
$this->nickname = $db_row->user_nickname;
           
$this->locale = $db_row->user_locale;
           
$this->email = $db_row->user_email;
           
$this->status = $db_row->user_status;
           
$this->url = $db_row->user_url;
           
$this->datecreated = $db_row->user_created_datetime;
           
$this->lastseen_ts = $db_row->user_lastseen_ts;
           
$this->level = $db_row->user_level;
           
$this->unsubscribe_key = $db_row->user_unsubscribe_key;
           
$this->gender = $db_row->user_gender;
           
$this->avatar_file_ID = $db_row->user_avatar_file_ID;
           
$this->reg_ctry_ID = $db_row->user_reg_ctry_ID;
           
$this->ctry_ID = $db_row->user_ctry_ID;
           
$this->rgn_ID = $db_row->user_rgn_ID;
           
$this->subrg_ID = $db_row->user_subrg_ID;
           
$this->city_ID = $db_row->user_city_ID;
           
$this->source = $db_row->user_source;
           
$this->profileupdate_date = $db_row->user_profileupdate_date;
           
$this->birthday_year = $db_row->user_birthday_year;
           
$this->birthday_month = $db_row->user_birthday_month;
           
$this->birthday_day = $db_row->user_birthday_day;

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


   
/**
     * Get this class db table config params
     *
     * @return array
     */
   
static function get_class_db_config()
    {
        static
$user_db_config;

        if( !isset(
$user_db_config ) )
        {
           
$user_db_config = array_merge( parent::get_class_db_config(),
                array(
                   
'dbtablename'        => 'T_users',
                   
'dbprefix'           => 'user_',
                   
'dbIDname'           => 'user_ID',
                )
            );
        }

        return
$user_db_config;
    }


   
/**
     * Get delete restriction settings
     *
     * @return array
     */
   
static function get_delete_restrictions()
    {
       
// 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...
        // asimo> Delete cascades and restrictions handling can't be handled globally in all circumstances because in a few places there is a logic behind the deletion:
        //        e.g. Delete user as spammer or simply delete the user
        // asimo> For sure in those cases when the cascade has no condition it would be faster to handled in on the db level.
        //        The current db_delete_where solution handles the delete cascade generally and handle the speciel cases in the cascaded classes as well ( e.g. delete files )
       
return 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_users__organization', 'fk'=>'org_owner_user_ID', 'msg'=>T_('%d organizations') ),
                array(
'table'=>'T_polls__question', 'fk'=>'pqst_owner_user_ID', 'msg'=>T_('%d polls owned by this user') ),
                array(
'table'=>'T_automation__automation', 'fk'=>'autm_owner_user_ID', 'msg'=>T_('%d automations') ),
               
// Do not delete user private messages
                //array( 'table'=>'T_messaging__message', 'fk'=>'msg_author_user_ID', 'msg'=>T_('The user has authored %d message(s)') ),
                //array( 'table'=>'T_messaging__threadstatus', 'fk'=>'tsta_user_ID', 'msg'=>T_('The user is part of %d messaging thread(s)') ),
           
);
    }


   
/**
     * Get delete cascade settings
     *
     * @return array
     */
   
static function get_delete_cascades()
    {
        return 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'),
                       
'class'=>'Session', 'class_path'=>'sessions/model/_session.class.php' ),
                array(
'table'=>'T_coll_user_perms', 'fk'=>'bloguser_user_ID', 'msg'=>T_('%d user permissions on blogs') ),
                array(
'table'=>'T_comments__votes', 'fk'=>'cmvt_user_ID', 'msg'=>T_('%d user votes on comments') ),
                array(
'table'=>'T_subscriptions', 'fk'=>'sub_user_ID', 'msg'=>T_('%d blog subscriptions') ),
                array(
'table'=>'T_items__item', 'fk'=>'post_creator_user_ID', 'msg'=>T_('%d posts created by this user'),
                       
'class'=>'Item', 'class_path'=>'items/model/_item.class.php', 'style' => 'bold' ),
                array(
'table'=>'T_items__subscriptions', 'fk'=>'isub_user_ID', 'msg'=>T_('%d post subscriptions') ),
                array(
'table'=>'T_messaging__contact', 'fk'=>'mct_to_user_ID', 'msg'=>T_('%d contacts from other users contact list') ),
                array(
'table'=>'T_messaging__contact', 'fk'=>'mct_from_user_ID', 'msg'=>T_('%d contacts from this user contact list') ),
                array(
'table'=>'T_messaging__contact_groups', 'fk'=>'cgr_user_ID', 'msg'=>T_('%d contact groups') ),
                array(
'table'=>'T_messaging__contact_groupusers', 'fk'=>'cgu_user_ID', 'msg'=>T_('%d contacts from contact groups') ),
                array(
'table'=>'T_pluginusersettings', 'fk'=>'puset_user_ID', 'msg'=>T_('%d user settings on plugins') ),
                array(
'table'=>'T_users__fields', 'fk'=>'uf_user_ID', 'msg'=>T_('%d user fields') ),
                array(
'table'=>'T_items__user_data', 'fk'=>'itud_user_ID', 'msg'=>T_('%d recordings of user data for a specific post') ),
                array(
'table'=>'T_links', 'fk'=>'link_usr_ID', 'msg'=>T_('%d links to this user'),
                       
'class'=>'Link', 'class_path'=>'links/model/_link.class.php' ),
                array(
'table'=>'T_files', 'fk'=>'file_root_ID', 'and_condition'=>'file_root_type = "user"', 'msg'=>T_('%d files from this user file root'), 'style' => 'bold' ),
                array(
'table'=>'T_email__campaign_send', 'fk'=>'csnd_user_ID', 'msg'=>T_('%d list emails for this user') ),
                array(
'table'=>'T_users__reports', 'fk'=>'urep_target_user_ID', 'msg'=>T_('%d reports about this user') ),
                array(
'table'=>'T_users__reports', 'fk'=>'urep_reporter_ID', 'msg'=>T_('%d reports created by this user') ),
                array(
'table'=>'T_users__user_org', 'fk'=>'uorg_user_ID', 'msg'=>T_('%d organization membership') ),
                array(
'table'=>'T_users__usertag', 'fk'=>'uutg_user_ID', 'msg'=>T_('%d links to tags') ),
                array(
'table'=>'T_polls__answer', 'fk'=>'pans_user_ID', 'msg'=>T_('%d poll answers') ),
                array(
'table'=>'T_users__secondary_user_groups', 'fk'=>'sug_user_ID', 'msg'=>T_('%d secondary groups') ),
                array(
'table'=>'T_users__profile_visits', 'fk'=>'upv_visited_user_ID', 'msg'=>T_('%d profile visits') ),
                array(
'table'=>'T_users__profile_visit_counters', 'fk'=>'upvc_user_ID', 'msg'=>T_('%d profile visit counter' ) ),
                array(
'table'=>'T_users__social_network', 'fk'=>'usn_user_ID', 'msg'=>T_('%d connected social networks') ),
                array(
'table'=>'T_email__newsletter_subscription', 'fk'=>'enls_user_ID', 'msg'=>T_('%d list subscriptions') ),
                array(
'table'=>'T_automation__user_state', 'fk'=>'aust_user_ID', 'msg'=>T_('%d states of User in Automation') ),
            );
    }


   
/**
     * Initialize relations for restrict and cascade deletion.
     * Spammer user's deletion will cascade more items.
     *
     * @param boolean Is this user spammer
     */
   
function init_relations( $params = array() )
    {
        if( !
is_null( $this->delete_cascades ) || ! is_null( $this->delete_restrictions ) )
        {
// Initialize the relations only once
           
return;
        }

       
$params = array_merge( array(
               
'delete_messages' => false,
               
'delete_comments' => false,
               
'delete_files'    => false,
            ),
$params );

       
parent::init_relations();

        if(
$params['delete_messages'] )
        {    
// Delete messages:
           
$this->delete_cascades[] = array( 'table'=>'T_messaging__message', 'fk'=>'msg_author_user_ID', 'msg'=>T_('%d messages from this user'),
                   
'class'=>'Message', 'class_path'=>'messaging/model/_message.class.php', 'style' => 'bold' );
           
$this->delete_cascades[] = array( 'table'=>'T_messaging__threadstatus', 'fk'=>'tsta_user_ID', 'msg'=>T_('%d message read statuses from this user') );
        }
        if(
$params['delete_comments'] )
        {    
// Delete comments:
           
$this->delete_cascades[] = array( 'table'=>'T_comments', 'fk'=>'comment_author_user_ID', 'msg'=>T_('%d comments by this user'),
                   
'class'=>'Comment', 'class_path'=>'comments/model/_comment.class.php', 'style' => 'bold' );
        }
        if(
$params['delete_files'] )
        {    
// Delete files and links:
           
$this->delete_cascades[] = array( 'table'=>'T_links', 'fk'=>'link_creator_user_ID', 'msg'=>T_('%d links created by this user'),
                   
'class'=>'Link', 'class_path'=>'links/model/_link.class.php' );
           
$this->delete_cascades[] = array( 'table'=>'T_files', 'fk'=>'file_creator_user_ID', 'and_condition'=>'file_root_type != "user"', 'msg'=>T_('%d files will lose their creator ID.') );
        }
    }


   
/**
     * Get link to restricted object
     *
     * Used when try to delete an user which has at least one poll question
     *
     * @param array restriction
     * @return string|boolean Message with link to objects,
     *                        Empty string if no restriction for current table,
     *                        FALSE - if no rule for current table
     */
   
function get_restriction_link( $restriction )
    {
        if(
$restriction['table'] == 'T_polls__question' )
        {    
// Check restriction for poll questions:
           
global $DB, $admin_url;

           
// Get a count of poll questions
           
$polls_count_SQL = new SQL();
           
$polls_count_SQL->SELECT( 'COUNT( pqst_ID )' );
           
$polls_count_SQL->FROM( $restriction['table'] );
           
$polls_count_SQL->WHERE( $restriction['fk'].' = '.$this->ID );
           
$polls_count = $DB->get_var( $polls_count_SQL->get(), 0, NULL, 'Get all poll questions of the user to check restriction befor deleting' );

            if(
$polls_count > 0 )
            {    
// Display this restriction as link to polls list with filter by this user login:
               
$msg = sprintf( $restriction['msg'], $polls_count );
                if(
check_user_perm( 'polls', 'view' ) )
                {    
// Set a link to poll questions list with filter by this user if current user has a perm to view all polls:
                   
$msg = '<a href="'.$admin_url.'?ctrl=polls&amp;owner='.$this->login.'">'.$msg.'</a>';
                }
                return
$msg;
            }
            else
            {    
// No restriction for current table:
               
return '';
            }
        }

       
// No rule for current table:
       
return false;
    }


   
/**
     * Check relations for restrictions before deleting
     *
     * @param string
     * @param array list of foreign keys to ignore
     * @param boolean TRUE to display restriction as link to edit the record
     * @return boolean true if no restriction prevents deletion
     */
   
function check_delete( $restrict_title, $ignore = array(), $addlink = false )
    {
       
// Prepend user login before restriction messages to know what user is deleting:
       
$user_login_link = $this->get_identity_link( array(
               
'link_text' => 'login',
            ) );

        return
parent::check_delete( $user_login_link.': '.$restrict_title, $ignore, true );
    }


   
/**
     * Load data from registration form
     *
     * @return boolean true if loaded data seems valid.
     */
   
function load_from_register_form()
    {
        global
$DB, $Settings, $UserSettings, $GroupCache, $Messages, $action;
        global
$current_User, $Session, $localtimenow;
        global
$is_api_request;

       
// TRUE when we create new user:
       
$is_new_user = ( $this->ID == 0 );

       
// TRUE if we should request a second password to confirm:
        // (Don't request it when we create new user from REST API)
       
$request_password_confirmation = ! ( $is_api_request && $is_new_user );

       
$has_full_access = check_user_perm( 'users', 'edit' );

       
// ---- Login checking / START ----
       
$edited_user_login = $this->check_login();
       
// ---- Login checking / END ----

        // ---- Password checking / START ----
        // TODO: Must be factorized with code from $this->load_from_Request()!
       
$reqID = param( 'reqID', 'string', '' );

        global
$edited_user_pass1, $edited_user_pass2;

       
$edited_user_pass1 = param( 'edited_user_pass1', 'string', true );
       
// Remove the invalid chars from password var:
       
$edited_user_pass1 = preg_replace( '/[<>&]/', '', $edited_user_pass1 );

        if(
$request_password_confirmation )
        {    
// Request a password confirmation:
           
$edited_user_pass2 = param( 'edited_user_pass2', 'string', true );
           
// Remove the invalid chars from password var:
           
$edited_user_pass2 = preg_replace( '/[<>&]/', '', $edited_user_pass2 );
        }

        if(
$is_new_user ||
                ( ! empty(
$reqID ) && $reqID == $Session->get( 'core.changepwd.request_id' ) ) ||
                (
$this->get( 'pass_driver' ) == 'nopass' ) )
        {    
// Current password is not required:
            //   - new user creating process
            //   - password change requested by email
            //   - password has not been set yet(email capture/quick registration)

           
if( $request_password_confirmation )
            {    
// Request a password confirmation:
               
if( param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen') ) )
                {
// We can set password
                   
$this->set_password( $edited_user_pass2 );
                }
            }
            else
            {    
// Don't request a password confirmation and use only first entered password (Used on REST API):
               
$this->set_password( $edited_user_pass1 );
            }
        }
        else
        {
           
// ******* Password edit form ****** //

           
$current_user_pass = param( 'current_user_pass', 'string', true );

            if(
$this->ID != $current_User->ID )
            {
// Set the messages when admin changes a password of other user
               
$checkpwd_params = array(
                       
'msg_pass_new'   => T_('Please enter new password.'),
                       
'msg_pass_twice' => T_('Please enter new password twice.'),
                    );
            }
            else
            {
// Use default messages
               
$checkpwd_params = array();
            }

            if( !
strlen( $current_user_pass ) )
            {
               
param_error( 'current_user_pass' , T_('Please enter your current password.') );
               
param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen'), $checkpwd_params );
            }
            else
            {
                if(
$has_full_access && $this->ID != $current_User->ID )
                {    
// Admin is changing a password of other user, Check a password of current admin
                   
$pass_User = $current_User;
                }
                else
                {    
// User is changing own password
                   
$pass_User = $this;
                }
                if(
$pass_User->check_password( $current_user_pass ) )
                {
                    if(
param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen'), $checkpwd_params ) )
                    {
// We can set password
                       
$this->set_password( $edited_user_pass2 );
                    }
                }
                else
                {
                   
param_error('current_user_pass' , T_('Your current password is incorrect.') );
                   
param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen'), $checkpwd_params );
                }
            }

        }

        if(
$_POST['edited_user_pass1'] != trim( $_POST['edited_user_pass1'] ) ||
               
$_POST['edited_user_pass2'] != trim( $_POST['edited_user_pass2'] ) )
        {
// If new password was entered with spaces then inform user about this
           
$Messages->add( T_('The leading and traling spaces have been trimmed from the new password.'), 'warning' );
        }
       
// ---- Password checking / END ----

        // Email is always required:
       
$edited_user_email = utf8_strtolower( param( 'edited_user_email', 'string', true ) );
       
param_check_not_empty( 'edited_user_email', T_('Please enter your e-mail address.') );
       
param_check_email( 'edited_user_email', true );
       
$this->set_email( $edited_user_email );

       
// Other fields:
       
$country = param( 'country', 'integer', '' );
       
$firstname = param( 'firstname', 'string', '' );
       
$lastname = param( 'lastname', 'string', NULL );
       
$gender = param( 'gender', 'string', NULL );
       
$locale = param( 'locale', 'string', '' );

       
// Set params:
       
$required_fields = get_registration_template_required_fields();

       
$paramsList = array();
        if(
in_array( 'country', $required_fields ) )
        {    
// Set and check country:
           
$this->set( 'ctry_ID', $country );
           
$paramsList['country'] = $country;
        }
        if(
in_array( 'firstname', $required_fields ) )
        {    
// Set and check first name:
           
$this->set( 'firstname', $firstname );
           
$paramsList['firstname'] = $firstname;
        }
        if(
in_array( 'lastname', $required_fields ) )
        {    
// Set last name:
           
$this->set( 'lastname', $lastname );
           
$paramsList['lastname'] = $lastname;
        }
        if(
in_array( 'gender', $required_fields ) )
        {    
// Set or check gender:
           
$this->set( 'gender', $gender );
           
$paramsList['gender'] = $gender;
        }
        if(
in_array( 'country', $required_fields ) )
        {    
// Only update locale without checking:
           
$this->set( 'locale', $locale );
        }
       
// Check profile params:
       
profile_check_params( $paramsList );

        return !
param_errors_detected();
    }


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

       
$has_full_access = check_user_perm( 'users', 'edit' );
       
$has_moderate_access = $current_User->can_moderate_user( $this->ID );

       
// TRUE when we create new user:
       
$is_new_user = ( $this->ID == 0 );

       
// TRUE if we should request a second password to confirm:
        // (Don't request it when we create new user from REST API)
       
$request_password_confirmation = ! ( $is_api_request && $is_new_user );

       
// ---- Login checking / START ----
       
$edited_user_login = $this->check_login();
       
// ---- Login checking / END ----

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

       
// ******* Admin form or new user create ******* //
        // In both cases current user must have users edit permission!
       
if( ( $is_admin_form || ( $is_identity_form && $is_new_user ) || ( $is_api_request && $is_new_user ) ) && $has_moderate_access )
        {
// level/group and email options are displayed on identity form only when creating a new user.
           
if( $this->ID != 1 )
            {
// the admin user group can't be changed
               
$edited_user_level = param( 'edited_user_level', 'integer', ! $is_api_request ? true : NULL );
                if( isset(
$edited_user_level ) )
                {
                   
param_integer_range( 'edited_user_level', 0, 10, T_('User level must be between %d and %d.'), true );
                   
$this->set_from_Request( 'level', 'edited_user_level', true );
                }

               
$edited_user_Group = param( 'edited_user_grp_ID', 'integer' );
                if( isset(
$edited_user_Group ) )
                {
                   
$edited_user_Group = $GroupCache->get_by_ID( $edited_user_Group );
                    if(
$edited_user_Group->can_be_assigned() )
                    {    
// Update group only if current user has a permission for this:
                       
$this->set_Group( $edited_user_Group );
                    }
                }
            }

           
$edited_user_secondary_grp_IDs = param( 'edited_user_secondary_grp_ID', 'array:integer', NULL );
            if(
$edited_user_secondary_grp_IDs !== NULL )
            {    
// Save secondary groups for this user:
                // Store old groups in order to delete them before insert new selected groups:
               
$this->old_secondary_groups = $this->get_secondary_groups();
               
$GroupCache = & get_GroupCache();
               
$GroupCache->clear();
               
// Set new groups which should be stored in DB:
               
$this->secondary_groups = $GroupCache->load_list( $edited_user_secondary_grp_IDs );
            }

           
$edited_user_source = param( 'edited_user_source', 'string', ! $is_api_request ? true : NULL );
            if( isset(
$edited_user_source ) )
            {
               
$this->set_from_Request('source', 'edited_user_source', true);
            }

           
// set email, without changing the user status
           
$edited_user_email = param( 'edited_user_email', 'string', ( ( ! $is_api_request ) || ( $is_api_request && $is_new_user ) ) ? true : NULL );
            if( isset(
$edited_user_email ) )
            {
               
$edited_user_email = utf8_strtolower( $edited_user_email );
               
param_check_not_empty( 'edited_user_email', T_('Please enter your e-mail address.') );
               
param_check_email( 'edited_user_email', true );
               
$this->set_email( $edited_user_email, false );
            }

            if(
$is_admin_form )
            {    
// Admin form
               
$notification_sender_email = param( 'notification_sender_email', 'string', ! $is_api_request ? true : NULL );
                if( isset(
$notification_sender_email ) )
                {
                   
$notification_sender_email = utf8_strtolower( $notification_sender_email );
                   
param_check_email( 'notification_sender_email' );
                    if( ! empty(
$notification_sender_email ) )
                    {
// Change a value of setting
                       
$UserSettings->set( 'notification_sender_email', $notification_sender_email, $this->ID );
                    }
                    elseif(
$UserSettings->get( 'notification_sender_email' , $this->ID ) != '' )
                    {
// Delete a setting record from DB
                       
$UserSettings->delete( 'notification_sender_email', $this->ID );
                    }
                }

               
$notification_sender_name = param( 'notification_sender_name', 'string', ! $is_api_request ? true : NULL );
                if( isset(
$notification_sender_name ) )
                {
                    if( ! empty(
$notification_sender_name ) )
                    {
// Change a value of setting
                       
$UserSettings->set( 'notification_sender_name', $notification_sender_name, $this->ID );
                    }
                    elseif(
$UserSettings->get( 'notification_sender_name' , $this->ID ) != '' )
                    {
// Delete a setting record from DB
                       
$UserSettings->delete( 'notification_sender_name', $this->ID );
                    }
                }

                if(
$has_full_access && !isset( $this->dbchanges['user_email'] ) )
                {    
// If email address is not changed
                    // Update status of email address in the T_email_address table
                   
$edited_email_status = param( 'edited_email_status', 'string' );
                   
$EmailAddressCache = & get_EmailAddressCache();
                   
$EmailAddress = & $EmailAddressCache->get_by_name( $this->get( 'email' ), false, false );
                    if( !
$EmailAddress && $edited_email_status != 'unknown' )
                    {    
// Create new record in the T_email_address table
                       
$EmailAddress = new EmailAddress();
                       
$EmailAddress->set( 'address', $this->get( 'email' ) );
                    }
                    if( !empty(
$EmailAddress ) )
                    {    
// Save status of an email address
                       
$EmailAddress->set( 'status', $edited_email_status );
                       
$EmailAddress->dbsave();
                    }
                }

                if(
check_user_perm( 'spamblacklist', 'edit' ) )
                {
// User can edit IP ranges
                    // Update status of IP range in DB
                   
$edited_iprange_status = param( 'edited_iprange_status', 'string' );
                   
$IPRangeCache = & get_IPRangeCache();
                   
$IPRange = & $IPRangeCache->get_by_ip( int2ip( $UserSettings->get( 'created_fromIPv4', $this->ID ) ) );
                    if( !
$IPRange && !empty( $edited_iprange_status ) )
                    {
// IP range doesn't exist in DB, Create new record
                       
$ip_24bit_start = ip2int( preg_replace( '#\.\d{1,3}$#i', '.0', int2ip( $UserSettings->get( 'created_fromIPv4', $this->ID ) ) ) );
                       
$ip_24bit_end = ip2int( preg_replace( '#\.\d{1,3}$#i', '.255', int2ip( $UserSettings->get( 'created_fromIPv4', $this->ID ) ) ) );
                       
$IPRange = new IPRange();
                       
$IPRange->set( 'IPv4start', $ip_24bit_start );
                       
$IPRange->set( 'IPv4end', $ip_24bit_end );
                       
$IPRange->set( 'user_count', 1 );
                    }
                    if(
$IPRange )
                    {
// Save status of IP range
                       
if( $IPRange->get( 'status' ) != 'blocked' && $edited_iprange_status == 'blocked' )
                        {    
// Status was changed to blocked, we should increase counter
                           
$IPRange->set( 'block_count', $IPRange->block_count + 1 );
                        }
                        else if(
$IPRange->get( 'status' ) == 'blocked' && $edited_iprange_status != 'blocked' )
                        {
// Status was changed from blocked to another, we should decrease counter
                           
$IPRange->set( 'block_count', $IPRange->block_count - 1 );
                        }
                       
$IPRange->set( 'status', $edited_iprange_status, true );
                       
$IPRange->dbsave();
                    }
                }

                if(
check_user_perm( 'stats', 'edit' ) )
                {
// User can edit Domains
                   
$DomainCache = & get_DomainCache();

                   
load_funcs('sessions/model/_hitlog.funcs.php');

                   
// Update status of email Domain in DB:
                   
$edited_email_domain_status = param( 'edited_email_domain_status', 'string' );
                   
$email_domain = $this->get_email_domain();
                   
$Domain = & get_Domain_by_subdomain( $email_domain );
                    if( !
$Domain && $edited_email_domain_status != 'unknown' && ! empty( $email_domain ) )
                    {    
// Domain doesn't exist in DB, Create new record:
                       
$Domain = new Domain();
                       
$Domain->set( 'name', $email_domain );
                    }
                    if(
$Domain )
                    {    
// Save status of Domain:
                       
$Domain->set( 'status', $edited_email_domain_status, true );
                       
$Domain->dbsave();
                    }

                   
// Update status of user Domain in DB:
                   
$edited_domain_status = param( 'edited_domain_status', 'string' );
                   
$user_domain = $UserSettings->get( 'user_registered_from_domain', $this->ID );
                   
$Domain = & get_Domain_by_subdomain( $user_domain );
                    if( !
$Domain && $edited_domain_status != 'unknown' && ! empty( $user_domain ) )
                    {
// Domain doesn't exist in DB, Create new record
                       
$Domain = new Domain();
                       
$Domain->set( 'name', $user_domain );
                    }
                    if(
$Domain )
                    {
// Save status of Domain
                       
$Domain->set( 'status', $edited_domain_status, true );
                       
$Domain->dbsave();
                    }

                   
// Update status of Initial referer in DB
                   
$edited_initial_referer_status = param( 'edited_initial_referer_status', 'string' );
                   
$initial_referer = $UserSettings->get( 'initial_referer', $this->ID );
                   
$initial_referer_domain = url_part( $initial_referer, 'host' );
                   
$Domain = & get_Domain_by_url( $initial_referer );
                    if( !
$Domain && $edited_initial_referer_status != 'unknown' && ! empty( $initial_referer_domain ) )
                    {
// Domain doesn't exist in DB, Create new record
                       
$Domain = new Domain();
                       
$Domain->set( 'name', $initial_referer_domain );
                    }
                    if(
$Domain )
                    {
// Save status of Domain
                       
$Domain->set( 'status', $edited_initial_referer_status, true );
                       
$Domain->dbsave();
                    }
                }
            }
        }

        if(
$has_moderate_access )
        {    
// If current user can moderate other users:
           
if( param( 'edited_user_tags', 'string', NULL ) !== NULL )
            {    
// Update user tags if they has been submitted:
               
$this->set_usertags_from_string( get_param( 'edited_user_tags' ) );
            }
        }

       
// ******* Identity form ******* //
       
if( $is_identity_form || ( $is_api_request && $is_new_user ) )
        {
           
$edited_user_perms = array( 'edited-user', 'edited-user-required' );

            if(
$Settings->get( 'self_selected_age_group') != 'hidden' )
            {
                global
$edited_user_age_min, $edited_user_age_max;
               
$age_group_is_required = $Settings->get( 'self_selected_age_group' ) == 'required';
               
$age_min = param( 'edited_user_age_min', 'integer', ! $is_api_request || $age_group_is_required ? true : NULL );
               
$age_max = param( 'edited_user_age_max', 'integer', ! $is_api_request || $age_group_is_required ? true : NULL );
                if( isset(
$age_min ) || isset( $age_max ) || $is_identity_form )
                {
                   
param_check_interval( 'edited_user_age_min', 'edited_user_age_max', T_('Age must be a number.'), T_('The first age must be lower than (or equal to) the second.'), $age_group_is_required );
                    if( !
param_has_error( 'edited_user_age_min' ) && $Settings->get( 'minimum_age' ) > 0 &&
                            !empty(
$edited_user_age_min ) && $edited_user_age_min < $Settings->get( 'minimum_age' ) )
                    {    
// Limit user by minimum age
                       
param_error( 'edited_user_age_min', sprintf( T_('You must be at least %d years old to use this service.'), $Settings->get( 'minimum_age' ) ) );
                    }
                   
$this->set_from_Request( 'age_min', 'edited_user_age_min', true );
                   
$this->set_from_Request( 'age_max', 'edited_user_age_max', true );
                }
            }

           
$birthdate = array();
            if(
$Settings->get( 'birthday_month' ) != 'hidden' )
            {    
// Save Birthday Month
               
$birthday_month_is_required = $Settings->get( 'birthday_month' ) == 'required';
               
$edited_user_birthday_month = param( 'edited_user_birthday_month', 'integer', ! $is_api_request || $birthday_month_is_required ? true : NULL );
                if( isset(
$edited_user_birthday_month ) || $is_identity_form )
                {
                    if(
$birthday_month_is_required && $has_moderate_access && empty( $edited_user_birthday_month ) )
                    {
                       
param_add_message_to_Log( 'edited_user_birthday_month', T_('Please select a birthday month').'.', 'warning', T_('Validation errors:') );
                    }
                    else
                    {
                       
param_check_number( 'edited_user_birthday_month', T_('Please select a birthday month').'.', $birthday_month_is_required );
                    }
                   
// Check for valid values:
                   
if( isset( $edited_user_birthday_month ) )
                    {
                       
param_integer_range( 'edited_user_birthday_month', 1, 12,
                               
sprintf( T_('The birthday month must be between %d and %d.'), 1, 12 ),
                                !
$is_api_request || $birthday_month_is_required ? true : NULL );

                       
$birthdate['month'] = $edited_user_birthday_month;
                    }
                   
$this->set_from_Request( 'birthday_month', 'edited_user_birthday_month', true );
                }
            }

            if(
$Settings->get( 'birthday_day' ) != 'hidden' )
            {    
// Save Birthday Day
               
$birthday_day_is_required = $Settings->get( 'birthday_day' ) == 'required';
               
$edited_user_birthday_day = param( 'edited_user_birthday_day', 'integer', ! $is_api_request || $birthday_day_is_required ? true : NULL );
                if( isset(
$edited_user_birthday_day ) || $is_identity_form )
                {
                    if(
$birthday_day_is_required && $has_moderate_access && empty( $edited_user_birthday_day ) )
                    {
                       
param_add_message_to_Log( 'edited_user_birthday_day', T_('Please select a birthday day').'.', 'warning', T_('Validation errors:') );
                    }
                    else
                    {
                       
param_check_number( 'edited_user_birthday_day', T_('Please select a birthday day').'.', $birthday_day_is_required );
                    }
                   
// Chehck for valid values:
                   
if( isset( $edited_user_birthday_day ) )
                    {
                       
param_integer_range( 'edited_user_birthday_day', 1, 31,
                               
sprintf( T_('The birthday day must be between %d and %d.'), 1, 31 ),
                                !
$is_api_request || $birthday_day_is_required ? true : NULL );

                       
$birthdate['day'] = $edited_user_birthday_day;
                    }
                   
$this->set_from_Request( 'birthday_day', 'edited_user_birthday_day', true );
                }
            }

            if(
$Settings->get( 'birthday_year' ) != 'hidden' )
            {    
// Save Birthday Year
               
$birthday_year_is_required = $Settings->get( 'birthday_year' ) == 'required';
               
$edited_user_birthday_year = param( 'edited_user_birthday_year', 'integer', ! $is_api_request || $birthday_year_is_required ? true : NULL );
                if( isset(
$edited_user_birthday_year ) || $is_identity_form )
                {
                    if(
$birthday_year_is_required && $has_moderate_access && empty( $edited_user_birthday_year ) )
                    {
                       
param_add_message_to_Log( 'edited_user_birthday_year', T_('Please select a birthday year').'.', 'warning', T_('Validation errors:') );
                    }
                    else
                    {
                       
param_check_number( 'edited_user_birthday_year', T_('Please select a birthday year').'.', $birthday_year_is_required );
                    }
                   
// Check for valid values:
                   
if( isset( $edited_user_birthday_year ) )
                    {
                       
param_integer_range( 'edited_user_birthday_year', 1900, (int) date( 'Y' ),
                               
sprintf( T_('The birthday year must be between %d and %d.'), 1900, (int) date( 'Y' ) ),
                                !
$is_api_request || $birthday_year_is_required ? true : NULL );

                       
$birthdate['year'] = $edited_user_birthday_year;
                        if(
$Settings->get( 'minimum_age' ) > 0 && ! empty( $edited_user_birthday_year ) )
                        {    
// We can only assume age if year is provided:
                           
if( empty( $birthdate['month']) )
                            {    
// Assume latest month:
                               
$birthdate['month'] = 12;
                            }
                            if( empty(
$birthdate['day']) )
                            {    
// Assume latest day of the month-year:
                               
$temp_date = strtotime( $birthdate['year'].'-'.$birthdate['month'].'-01' );
                               
$birthdate['day'] = (int) date( 't', $temp_date );
                            }

                           
$birth_date = new DateTime();
                           
$today     = new DateTime();
                           
$birth_date->setDate( $birthdate['year'], $birthdate['month'], $birthdate['day'] );
                           
$interval = date_diff( $today, $birth_date );
                           
$age = (int) $interval->format( '%y' );
                            if(
$age < $Settings->get( 'minimum_age' ) )
                            {
                               
param_error( 'edited_user_birthday_year', sprintf( T_('You must be at least %d years old to use this service.'), $Settings->get( 'minimum_age' ) ) );
                            }
                        }
                    }
                   
$this->set_from_Request( 'birthday_year', 'edited_user_birthday_year', true );
                }
            }

           
$firstname_editing = $Settings->get( 'firstname_editing' );
            if( (
in_array( $firstname_editing, $edited_user_perms ) && $this->ID == $current_User->ID ) || ( $firstname_editing != 'hidden' && $has_moderate_access ) )
            {    
// User has a permissions to save Firstname
               
$edited_user_firstname = param( 'edited_user_firstname', 'string', ! $is_api_request || $firstname_editing == 'edited-user-required' ? true : NULL );
                if( isset(
$edited_user_firstname ) || $is_identity_form )
                {
                    if(
$firstname_editing == 'edited-user-required' )
                    {    
// First name is required
                       
param_check_not_empty( 'edited_user_firstname', T_('Please enter your first name.') );
                    }
                   
$this->set_from_Request('firstname', 'edited_user_firstname', true);
                }
            }

           
$lastname_editing = $Settings->get( 'lastname_editing' );
            if( (
in_array( $lastname_editing, $edited_user_perms ) && $this->ID == $current_User->ID ) || ( $lastname_editing != 'hidden' && $has_moderate_access ) )
            {    
// User has a permissions to save Lastname
               
$edited_user_lastname = param( 'edited_user_lastname', 'string', ! $is_api_request || $lastname_editing == 'edited-user-required' ? true : NULL );
                if( isset(
$edited_user_lastname ) || $is_identity_form )
                {
                    if(
$lastname_editing == 'edited-user-required' )
                    {    
// Last name is required
                       
param_check_not_empty( 'edited_user_lastname', T_('Please enter last name.') );
                    }
                   
$this->set_from_Request('lastname', 'edited_user_lastname', true);
                }
            }

           
$nickname_editing = $Settings->get( 'nickname_editing' );
            if( (
in_array( $nickname_editing, $edited_user_perms ) && $this->ID == $current_User->ID ) || ( $nickname_editing != 'hidden' && $has_moderate_access ) )
            {    
// User has a permissions to save Nickname
               
$edited_user_nickname = param( 'edited_user_nickname', 'string', ! $is_api_request || $nickname_editing == 'edited-user-required' ? true : NULL );
                if( isset(
$edited_user_nickname ) || $is_identity_form )
                {
                    if(
$nickname_editing == 'edited-user-required' )
                    {    
// Nickname is required
                       
param_check_not_empty( 'edited_user_nickname', T_('Please enter your nickname.') );
                    }
                   
$this->set_from_Request('nickname', 'edited_user_nickname', true);
                }
            }

            if(
$this->ID == $current_User->ID || $has_moderate_access )
            {    
// No user latitude setting:
               
$edited_user_gender = param( 'edited_user_gender', 'string' );
                if( isset(
$edited_user_gender ) || $is_identity_form )
                {
                   
$this->set_from_Request('gender', 'edited_user_gender', true);
                }
            }

           
// ---- Locations / START ----
           
load_funcs( 'regional/model/_regional.funcs.php' );

            if(
user_country_visible() )
            {
// Save country
               
$country_is_required = ( $Settings->get( 'location_country' ) == 'required' && countries_exist() );
               
$edited_user_ctry_ID = param( 'edited_user_ctry_ID', 'integer', ! $is_api_request || $country_is_required ? true : NULL );
                if( isset(
$edited_user_ctry_ID ) || $is_identity_form )
                {
                    if(
$country_is_required && $has_moderate_access && $edited_user_ctry_ID == 0 )
                    {
// Display a note message if user can edit all users
                       
param_add_message_to_Log( 'edited_user_ctry_ID', T_('Please select a country').'.', 'note' );
                    }
                    else
                    {
// Display an error message
                       
param_check_number( 'edited_user_ctry_ID', T_('Please select a country').'.', $country_is_required );
                    }
                   
$this->set_from_Request( 'ctry_ID', 'edited_user_ctry_ID', true );
                }
            }

            if(
user_region_visible() )
            {
// Save region
               
$region_is_required = ( $Settings->get( 'location_region' ) == 'required' && regions_exist( $edited_user_ctry_ID ) );
               
$edited_user_rgn_ID = param( 'edited_user_rgn_ID', 'integer', ! $is_api_request || $region_is_required ? true : NULL );
                if( isset(
$edited_user_rgn_ID ) || $is_identity_form  )
                {
                    if(
$region_is_required && $has_moderate_access && $edited_user_rgn_ID == 0 )
                    {
// Display a note message if user can edit all users
                       
param_add_message_to_Log( 'edited_user_rgn_ID', T_('Please select a region').'.', 'note' );
                    }
                    else
                    {
// Display an error message
                       
param_check_number( 'edited_user_rgn_ID', T_('Please select a region'), $region_is_required );
                    }
                   
$this->set_from_Request( 'rgn_ID', 'edited_user_rgn_ID', true );
                }
            }

            if(
user_subregion_visible() )
            {
// Save subregion
               
$subregion_is_required = ( $Settings->get( 'location_subregion' ) == 'required' && subregions_exist( $edited_user_rgn_ID ) );
               
$edited_user_subrg_ID = param( 'edited_user_subrg_ID', 'integer', ! $is_api_request || $subregion_is_required ? true : NULL );
                if( isset(
$edited_user_subrg_ID ) || $is_identity_form  )
                {
                    if(
$subregion_is_required && $has_moderate_access && $edited_user_subrg_ID == 0 )
                    {
// Display a note message if user can edit all users
                       
param_add_message_to_Log( 'edited_user_subrg_ID', T_('Please select a sub-region').'.', 'note' );
                    }
                    else
                    {
// Display an error message
                       
param_check_number( 'edited_user_subrg_ID', T_('Please select a sub-region').'.', $subregion_is_required );
                    }
                   
$this->set_from_Request( 'subrg_ID', 'edited_user_subrg_ID', true );
                }
            }

            if(
user_city_visible() )
            {
// Save city
               
$city_is_required = ( $Settings->get( 'location_city' ) == 'required' && cities_exist( $edited_user_ctry_ID, $edited_user_rgn_ID, $edited_user_subrg_ID ) );
               
$edited_user_city_ID = param( 'edited_user_city_ID', 'integer', ! $is_api_request || $city_is_required ? true : NULL );
                if( isset(
$edited_user_city_ID ) || $is_identity_form )
                {
                    if(
$city_is_required && $has_moderate_access && $edited_user_city_ID == 0 )
                    {
// Display a note message if user can edit all users
                       
param_add_message_to_Log( 'edited_user_city_ID', T_('Please select a city').'.', 'note' );
                    }
                    else
                    {
// Display an error message
                       
param_check_number( 'edited_user_city_ID', T_('Please select a city').'.', $city_is_required );
                    }
                   
$this->set_from_Request( 'city_ID', 'edited_user_city_ID', true );
                }
            }
           
// ---- Locations / END ----


            // ---- Organizations / START ----
           
$organizations = param( 'organizations', 'array:string' );
           
$org_roles = param( 'org_roles', 'array:string' );
           
$org_priorities = param( 'org_priorities', 'array:string' );
           
$this->update_organizations( $organizations, $org_roles, $org_priorities );
           
// ---- Organizations / END ----


            // ---- Additional Fields / START ----

            // Load all defined userfields for following checking of required fields
           
$this->userfield_defs_load();

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

            foreach(
$userfield_IDs as $userfield )
            {
                if( ! isset(
$this->userfield_defs[$userfield->uf_ufdf_ID] ) )
                {    
// If user field definition doesn't exist in DB then delete field value of this user:
                   
$this->userfield_update( $userfield->uf_ID, NULL );
                    continue;
                }

                if( !
userfield_is_viewable( $this->ID, $this->userfield_defs[$userfield->uf_ufdf_ID][5], $this->userfield_defs[$userfield->uf_ufdf_ID][0] ) )
                {    
// Current user cannot update the user field:
                   
continue;
                }

               
$uf_val = param( 'uf_'.$userfield->uf_ID, 'raw' );
               
$field_type = $this->userfield_defs[$userfield->uf_ufdf_ID][0];
                if(
$field_type == 'number' )
                {    
// Change number type of integer because we have this type name preparing in function param_format():
                   
$field_type = 'integer';
                }
                elseif(
$field_type != 'text' && $field_type != 'url' )
                {    
// Use all other params as string, Only text and url have a preparing in function param_format():
                   
$field_type = 'string';
                }
               
$uf_val = param_format( $uf_val, $field_type );

                if(
$this->userfield_defs[$userfield->uf_ufdf_ID][0] == 'list' && $uf_val == '---' )
                {    
// Option list has a value '---' for empty value
                   
$uf_val = '';
                }

                if( empty(
$uf_val ) && $this->userfield_defs[$userfield->uf_ufdf_ID][2] == 'require' )
                {    
// Display error for empty required field
                   
if( $has_full_access )
                    {    
// Display a note message if user can edit all users
                       
param_add_message_to_Log( 'uf_'.$userfield->uf_ID, sprintf( T_('Please enter a value for the field "%s".'), $this->userfield_defs[$userfield->uf_ufdf_ID][1] ), 'note' );
                    }
                    else
                    {    
// Display an error message
                       
param_error( 'uf_'.$userfield->uf_ID, T_('Please enter a value.') );
                    }
                }
                else
                {    
// Update field
                   
if( $this->userfield_defs[$userfield->uf_ufdf_ID][0] == 'url' )
                    {    
// Check url fields
                       
param_check_url( 'uf_'.$userfield->uf_ID, 'commenting' );
                    }
                    if(
$this->userfield_defs[$userfield->uf_ufdf_ID][4] == 'list' )
                    {    
// Option "Multiple values" == "List style"
                        // Split by comma and save each phrase as separate field
                       
$uf_val = explode( ',', $uf_val );
                        foreach(
$uf_val as $v => $val )
                        {
                           
$val = trim( $val );
                            if(
$v == 0 )
                            {    
// Update field with first value
                               
$this->userfield_update( $userfield->uf_ID, $val );
                            }
                            else if( !empty(
$val ) )
                            {    
// Add a new field for new values
                               
$this->userfield_add( $userfield->uf_ufdf_ID, $val );
                            }
                        }
                    }
                    else
                    {    
// Forbidden & Allowed fields
                       
$this->userfield_update( $userfield->uf_ID, $uf_val );
                    }
                }
            }

           
// Duplicate fields:
           
if( $is_new_user )
            {
               
$user_id = param( 'orig_user_ID', 'integer', 0 );
                if(
$user_id !== 0 )
                {
                   
$SQL = new SQL( 'Get all user fields of duplicated user #'.$user_id );
                   
$SQL->SELECT( 'uf_ID, ufdf_ID, ufdf_type' );
                   
$SQL->FROM( 'T_users__fields' );
                   
$SQL->FROM_add( 'INNER JOIN T_users__fielddefs ON uf_ufdf_ID = ufdf_ID' );
                   
$SQL->WHERE( 'uf_user_ID = '.$user_id );
                   
$copied_userfields = $DB->get_results( $SQL );
                    foreach(
$copied_userfields as $copied_userfield )
                    {
                       
$uf_val = param( 'uf_'.$copied_userfield->uf_ID, 'raw' );
                       
$field_type = $copied_userfield->ufdf_type;
                        if(
$field_type == 'number' )
                        {    
// Change number type of integer because we have this type name preparing in function param_format():
                           
$field_type = 'integer';
                        }
                        elseif(
$field_type != 'text' && $field_type != 'url' )
                        {    
// Use all other params as string, Only text and url have a preparing in function param_format():
                           
$field_type = 'string';
                        }
                       
$uf_val = param_format( $uf_val, $field_type );
                        if( ! empty(
$uf_val ) )
                        {    
// Copy user field only if it is not empty:
                           
$this->userfield_add( $copied_userfield->ufdf_ID, $uf_val );
                        }
                    }
                }
            }

           
$uf_new_fields = param( 'uf_new', 'array' );    // Recommended & required fields (it still not saved in DB)
           
$uf_add_fields = param( 'uf_add', 'array' );    // Added fields

            // Add a new field: (JS is not enabled)
           
if( $action == 'add_field' )
            {    
// Button 'Add' new field is pressed
               
$new_field_type = param( 'new_field_type', 'integer', 0 );
                if( empty(
$new_field_type ) )
                {    
// We cannot add a new field without type
                   
param_error( 'new_field_type', T_('Please select a field type.') );
                }
                else
                {    
// Save an adding field(in the array) to display it again if errors will be exist
                   
$new_field_type_exists = false;

                    if(
$this->userfield_defs[$new_field_type][4] == 'allowed' || $this->userfield_defs[$new_field_type][4] == 'list' )
                    {    
// This field can be duplicated
                       
global $add_field_types;
                       
$add_field_types = array( $new_field_type );
                    }
                    else
                    {    
// We should to solve we can add this field or don't
                       
if( ! isset( $uf_new_fields[$new_field_type] ) && ! isset( $uf_add_fields[$new_field_type] ) )
                        {    
// User is adding this field first time
                           
if( is_array( $userfield_IDs ) && count( $userfield_IDs ) > 0 )
                            {    
// User has fields that saved in DB
                               
foreach( $userfield_IDs as $userfield )
                                {
                                    if(
$userfield->uf_ufdf_ID == $new_field_type )
                                    {    
// New adding field already exists for current user in DB
                                       
$new_field_type_exists = true;
                                        break;
                                    }
                                }
                            }
                            if( !
$new_field_type_exists )
                            {    
// Field doesn't still exist for current user
                               
global $add_field_types;
                               
$add_field_types = array( $new_field_type );
                            }
                        }
                        else
                        {    
// Field exists, no duplicates available
                           
$new_field_type_exists = true;
                        }

                        if(
$new_field_type_exists )
                        {    
// Field already is added for current user, we should display error about this
                           
param_error( 'new_field_type', T_('You already added this field, please select another.') );
                        }
                    }

                    if( !
$new_field_type_exists )
                    {    
// Mark a new field to enter a value
                       
param_error( 'uf_add['.$new_field_type.'][]', T_('Please enter a value in this new field.') );
                    }
                }
            }

           
// Save a New recommended & require fields AND Adding fields
           
if( count( $uf_new_fields ) > 0 || count( $uf_add_fields ) > 0 )
            {
               
$uf_fields = array(
                   
'new' => $uf_new_fields,
                   
'add' => $uf_add_fields
               
);
                foreach(
$uf_fields as $uf_type => $uf_new_fields )
                {
                    if(
$uf_type == 'add' )
                    {    
// Save an adding fields to display it again if errors will be exist
                       
global $add_field_types;
                        if( ! isset(
$add_field_types ) )
                        {    
// Don't rewrite already existing array
                           
$add_field_types = array();
                        }
                    }
                    foreach(
$uf_new_fields as $uf_new_id => $uf_new_vals )
                    {
                        if( !
userfield_is_viewable( $this->ID, $this->userfield_defs[$uf_new_id][5], $this->userfield_defs[$uf_new_id][0] ) )
                        {    
// Current user cannot add the user field:
                           
continue;
                        }

                        foreach(
$uf_new_vals as $uf_new_val )
                        {
                            if(
$this->userfield_defs[$uf_new_id][0] == 'list' && $uf_new_val == '---' )
                            {    
// Option list has a value '---' for empty value
                               
$uf_new_val = '';
                            }

                            if(
$uf_new_val != '' )
                            {    
// Insert a new field in DB if it is filled:
                               
switch( $this->userfield_defs[$uf_new_id][0] )
                                {
                                    case
'url':
                                       
// Format url field to valid value:
                                       
$uf_new_val = param_format( $uf_new_val, 'url' );
                                       
// Check url fields:
                                       
param_check_url( 'uf_'.$uf_type.'['.$uf_new_id.'][]', 'commenting' );
                                        break;

                                    case
'text':
                                       
// Format text field to valid value:
                                       
$uf_new_val = param_format( $uf_new_val, 'text' );
                                        break;

                                    case
'number':
                                       
// Format number field to valid value:
                                       
$uf_new_val = param_format( $uf_new_val, 'integer' );
                                        break;

                                    case
'email':
                                    case
'word':
                                    case
'phone':
                                       
// Format string fields to valid value:
                                       
$uf_new_val = param_format( $uf_new_val, 'string' );
                                        break;
                                }

                                if(
$this->userfield_defs[$uf_new_id][4] == 'list' )
                                {    
// Option "Multiple values" == "List style"
                                    // Split by comma and save each phrase as separate field
                                   
$uf_new_val = explode( ',', $uf_new_val );
                                    foreach(
$uf_new_val as $val )
                                    {
                                       
$val = param_format( $val, 'string' );
                                        if( !empty(
$val ) )
                                        {    
// Exclude empty values(spaces)
                                           
$this->userfield_add( (int)$uf_new_id, $val );
                                        }
                                    }
                                }
                                else
                                {    
// Forbidden & Allowed fields
                                   
$this->userfield_add( (int)$uf_new_id, $uf_new_val );
                                }
                            }
                            elseif( empty(
$uf_new_val ) && $this->userfield_defs[$uf_new_id][2] == 'require' )
                            {    
// Display error for empty required field & new adding field
                               
if( $has_full_access )
                                {    
// Display a note message if user can edit all users
                                   
param_add_message_to_Log( 'uf_'.$uf_type.'['.$uf_new_id.'][]', sprintf( T_('Please enter a value for the field "%s".'), $this->userfield_defs[$uf_new_id][1] ), 'note' );
                                }
                                else
                                {    
// Display an error message
                                   
param_error( 'uf_'.$uf_type.'['.$uf_new_id.'][]', T_('Please enter a value.') );
                                }
                            }

                            if(
$uf_type == 'add' )
                            {    
// Save new added field, it used on the _user_identity.form
                               
$add_field_types[] = $uf_new_id;
                            }
                        }
                    }
                }
            }
           
// ---- Additional Fields / END ----

            // update profileupdate_date, because a publicly visible user property was changed
           
$this->set_profileupdate_date();
        }

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

       
$is_password_form = param( 'password_form', 'boolean', false );
        if(
$is_password_form || $is_new_user )
        {
           
$reqID = param( 'reqID', 'string', '' );

            global
$edited_user_pass1, $edited_user_pass2;

           
$edited_user_pass1 = param( 'edited_user_pass1', 'string', true );
           
// Remove the invalid chars from password var:
           
$edited_user_pass1 = preg_replace( '/[<>&]/', '', $edited_user_pass1 );

            if(
$request_password_confirmation )
            {    
// Request a password confirmation:
               
$edited_user_pass2 = param( 'edited_user_pass2', 'string', true );
               
// Remove the invalid chars from password var:
               
$edited_user_pass2 = preg_replace( '/[<>&]/', '', $edited_user_pass2 );
            }

            if(
$is_new_user ||
                ( ! empty(
$reqID ) && $reqID == $Session->get( 'core.changepwd.request_id' ) ) ||
                (
$this->get( 'pass_driver' ) == 'nopass' ) )
            {    
// Current password is not required:
                //   - new user creating process
                //   - password change requested by email
                //   - password has not been set yet(email capture/quick registration)

               
if( $is_new_user && param( 'init_pass', 'string' ) == 'user' )
                {    
// Allow to create new user without password from back-office by admin:
                   
$this->set( 'pass_driver', 'nopass' );
                   
$this->set( 'pass', '' );
                   
$this->set( 'salt', '' );
                }
                elseif(
$request_password_confirmation )
                {    
// Request a password confirmation:
                   
if( param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen') ) )
                    {
// We can set password
                       
$this->set_password( $edited_user_pass2 );
                    }
                }
                else
                {    
// Don't request a password confirmation and use only first entered password (Used on REST API):
                   
$this->set_password( $edited_user_pass1 );
                }
            }
            else
            {
               
// ******* Password edit form ****** //

               
$current_user_pass = param( 'current_user_pass', 'string', true );

                if(
$this->ID != $current_User->ID )
                {
// Set the messages when admin changes a password of other user
                   
$checkpwd_params = array(
                           
'msg_pass_new'   => T_('Please enter new password.'),
                           
'msg_pass_twice' => T_('Please enter new password twice.'),
                        );
                }
                else
                {
// Use default messages
                   
$checkpwd_params = array();
                }

                if( !
strlen( $current_user_pass ) )
                {
                   
param_error( 'current_user_pass' , T_('Please enter your current password.') );
                   
param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen'), $checkpwd_params );
                }
                else
                {
                    if(
$has_full_access && $this->ID != $current_User->ID )
                    {    
// Admin is changing a password of other user, Check a password of current admin
                       
$pass_User = $current_User;
                    }
                    else
                    {    
// User is changing own password
                       
$pass_User = $this;
                    }
                    if(
$pass_User->check_password( $current_user_pass ) )
                    {
                        if(
param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen'), $checkpwd_params ) )
                        {
// We can set password
                           
$this->set_password( $edited_user_pass2 );
                        }
                    }
                    else
                    {
                       
param_error('current_user_pass' , T_('Your current password is incorrect.') );
                       
param_check_passwords( 'edited_user_pass1', 'edited_user_pass2', true, $Settings->get('user_minpwdlen'), $checkpwd_params );
                    }
                }

            }

            if(
$_POST['edited_user_pass1'] != trim( $_POST['edited_user_pass1'] ) ||
               
$_POST['edited_user_pass2'] != trim( $_POST['edited_user_pass2'] ) )
            {
// If new password was entered with spaces then inform user about this
               
$Messages->add( T_('The leading and traling spaces have been trimmed from the new password.'), 'warning' );
            }
        }


       
// Used in Preferences & Notifications forms
       
$has_messaging_perm = $this->check_perm( 'perm_messaging', 'reply' );

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

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

        if(
$is_preferences_form )
        {
           
// Other preferences
           
$preferred_locale = param( 'edited_user_locale', 'string', ! $is_api_request );
            if( isset(
$preferred_locale ) )
            {
               
$this->set_from_Request('locale', 'edited_user_locale', true);
            }

           
// Session timeout
           
$edited_user_timeout_sessions = param( 'edited_user_timeout_sessions', 'string', NULL );
            if( isset(
$edited_user_timeout_sessions ) && ( $current_User->ID == $this->ID  || $has_full_access ) )
            {
                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;
                }
            }

           
$edited_user_showonline = param( 'edited_user_showonline', 'integer', ! $is_api_request ? 0 : NULL );
            if( isset(
$edited_user_showonline ) )
            {
               
$UserSettings->set( 'show_online', $edited_user_showonline, $this->ID );
            }
        }

       
// ******* Notifications form ******* //
       
$is_subscriptions_form = param( 'subscriptions_form', 'boolean', false );
       
$notifications_mode = $Settings->get( 'outbound_notifications_mode' );
        if(
$is_subscriptions_form )
        {
            if(
$action == 'subscribe' )
            {
// Do only subscribe to new blog (Don't update the user's settings from the same form)

                // If notifications are temporarily disabled, no subscription options should be lost when user saves.
                // When notification are turned back on, the subscription will resume as they were before.
               
if( $notifications_mode != 'off' )
                {
                   
// A selected blog to subscribe
                   
$subscribe_blog_ID = param( 'subscribe_blog', 'integer', 0 );

                   
$sub_items    = 1;
                   
$sub_items_mod = 0;
                   
$sub_comments = 0; // We normally subscribe to new posts only

                    // Note: we do not check if subscriptions are allowed here, but we check at the time we're about to send something
                   
if( $subscribe_blog_ID )
                    {
// We need to record values:
                       
$BlogCache = & get_BlogCache();
                       
$subscribe_Blog = & $BlogCache->get_by_ID( $subscribe_blog_ID, false, false );

                        if(
$subscribe_Blog->has_access( $this ) )
                        {
// user has access to the collection

                            // erhsatingin > it seems we do need to check if subscription is allowed, at least for new posts
                           
if( ! $subscribe_Blog->get_setting( 'allow_subscriptions' ) )
                            {
// Subscription to new posts is not allowed so we subscribe to new comments instead
                               
$sub_comments = 1;
                            }

                           
$DB->query( 'REPLACE INTO T_subscriptions( sub_coll_ID, sub_user_ID, sub_items, sub_items_mod, sub_comments )
                                VALUES ( '
.$DB->quote( $subscribe_blog_ID ).', '.$DB->quote( $this->ID ).', '.$DB->quote( $sub_items ).', '.$DB->quote( $sub_items_mod ).', '.$DB->quote( $sub_comments ).' )' );

                           
$Messages->add( T_('Subscriptions have been changed.'), 'success' );
                        }
                        else
                        {
                           
$Messages->add( T_('User has no access to the selected blog.'), 'error' );
                        }
                    }
                    else
                    {
// Display an error message to inform user about incorrect actions
                       
$Messages->add( T_('Please select at least one setting to subscribe on the selected blog.'), 'error' );
                    }
                }
            }
            else
            {
// Update user's settings

                // Email communication
               
$edited_user_email = param( 'edited_user_email', 'string', ! $is_api_request ? true : NULL );
                if( isset(
$edited_user_email ) )
                {
                   
$edited_user_email = utf8_strtolower( $edited_user_email );
                   
param_check_not_empty( 'edited_user_email', T_('Please enter your e-mail address.') );
                   
param_check_email( 'edited_user_email', true );
                   
$this->set_email( $edited_user_email );
                }

               
// set messaging options
               
$enable_PM = param( 'enable_PM', 'integer', ! $is_api_request ? 0 : NULL );
                if( isset(
$enable_PM ) && $has_messaging_perm )
                {
                   
$UserSettings->set( 'enable_PM', $enable_PM, $this->ID );
                }
               
$emails_msgform = $Settings->get( 'emails_msgform' );
                if( (
$emails_msgform == 'userset' ) || ( ( $emails_msgform == 'adminset' ) && $has_full_access ) )
                {
// enable email option is displayed only if user can set or if admin can set and current User is an administrator
                   
$UserSettings->set( 'enable_email', param( 'enable_email', 'integer', 0 ), $this->ID );
                }

               
// Email format
               
$UserSettings->set( 'email_format', param( 'edited_user_email_format', 'string', 'auto' ), $this->ID );

               
// set notification options
               
if( $has_messaging_perm )
                {
// update 'notify messages' only if user has messaging rights and this option was displayed
                   
$UserSettings->set( 'notify_messages', param( 'edited_user_notify_messages', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_unread_messages', param( 'edited_user_notify_unread_messages', 'integer', 0 ), $this->ID );
                }
               
$UserSettings->set( 'notify_comment_mentioned', param( 'edited_user_notify_comment_mentioned', 'integer', 0 ), $this->ID );
                if(
$this->check_role( 'post_owner' ) )
                {
// update 'notify_published_comments' only if user has at least one post or user has right to create new post
                   
$UserSettings->set( 'notify_published_comments', param( 'edited_user_notify_publ_comments', 'integer', 0 ), $this->ID );
                }
               
$is_comment_moderator = $this->check_role( 'comment_moderator' );
                if(
$is_comment_moderator || $this->check_role( 'comment_editor' ) )
                {    
// update 'notify_comment_moderation', 'notify_edit_cmt_moderation' and 'notify_spam_cmt_moderation' only if user is comment moderator/editor at least in one collection:
                   
$UserSettings->set( 'notify_comment_moderation', param( 'edited_user_notify_cmt_moderation', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_edit_cmt_moderation', param( 'edited_user_notify_edit_cmt_moderation', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_spam_cmt_moderation', param( 'edited_user_notify_spam_cmt_moderation', 'integer', 0 ), $this->ID );
                }
                if(
$this->check_perm( 'admin', 'restricted', false ) )
                {    
// update 'notify_meta_comment_mentioned' ans 'notify_meta_comments' only if edited user has a permission to back-office:
                   
$UserSettings->set( 'notify_meta_comment_mentioned', param( 'edited_user_notify_meta_comment_mentioned', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_meta_comments', param( 'edited_user_notify_meta_comments', 'integer', 0 ), $this->ID );
                }
                if(
$is_comment_moderator )
                {
// update 'send_cmt_moderation_reminder' only if user is comment moderator at least in one blog
                   
$UserSettings->set( 'send_cmt_moderation_reminder', param( 'edited_user_send_cmt_moderation_reminder', 'integer', 0 ), $this->ID );
                }
               
$UserSettings->set( 'notify_post_mentioned', param( 'edited_user_notify_post_mentioned', 'integer', 0 ), $this->ID );
                if(
$this->check_role( 'post_moderator' ) )
                {    
// update 'notify_post_moderation', 'notify_edit_pst_moderation' and 'send_cmt_moderation_reminder' only if user is post moderator at least in one collection:
                   
$UserSettings->set( 'notify_post_moderation', param( 'edited_user_notify_post_moderation', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_edit_pst_moderation', param( 'edited_user_notify_edit_pst_moderation', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_post_proposed', param( 'edited_user_notify_post_proposed', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'send_pst_moderation_reminder', param( 'edited_user_send_pst_moderation_reminder', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'send_pst_stale_alert', param( 'edited_user_send_pst_stale_alert', 'integer', 0 ), $this->ID );
                }
                if(
$this->check_role( 'member' ) )
                {
                   
$UserSettings->set( 'notify_post_assignment', param( 'edited_user_notify_post_assignment', 'integer', 0 ), $this->ID );
                }
                if(
$has_full_access )
                {
                   
$UserSettings->set( 'send_activation_reminder', param( 'edited_user_send_activation_reminder', 'integer', 0 ), $this->ID );
                }
                if(
$Settings->get( 'inactive_account_reminder_threshold' ) > 0 )
                {    
// If setting "Trigger after" of cron job "Send reminders about inactive accounts" is selected at least to 1 second:
                   
$UserSettings->set( 'send_inactive_reminder', param( 'edited_user_send_inactive_reminder', 'integer', 0 ), $this->ID );
                }

                if(
$this->check_perm( 'users', 'edit' ) )
                {
// edited user has permission to edit all users, save notification preferences
                   
$UserSettings->set( 'notify_new_user_registration', param( 'edited_user_notify_new_user_registration', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_activated_account', param( 'edited_user_notify_activated_account', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_closed_account', param( 'edited_user_notify_closed_account', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_reported_account', param( 'edited_user_notify_reported_account', 'integer', 0 ), $this->ID );
                   
$UserSettings->set( 'notify_changed_account', param( 'edited_user_notify_changed_account', 'integer', 0 ), $this->ID );
                }

                if(
$this->check_perm( 'options', 'edit' ) )
                {
// edited user has permission to edit options, save notification preferences
                   
$UserSettings->set( 'notify_cronjob_error', param( 'edited_user_notify_cronjob_error', 'integer', 0 ), $this->ID );
                }

                if(
$has_full_access && $this->check_perm( 'options', 'view' ) )
                {    
// current User is an administrator and the edited user has a permission to automations:
                   
$UserSettings->set( 'notify_automation_owner', param( 'edited_user_notify_automation_owner', 'integer', 0 ), $this->ID );
                }

               
// Newsletters:
               
$newsletter_subscriptions = param( 'edited_user_newsletters', 'array:integer', NULL );
               
$this->set_newsletter_subscriptions( $newsletter_subscriptions );
               
$UserSettings->set( 'notify_list_new_subscriber', param( 'edited_user_notify_list_new_subscriber', 'integer', 0 ), $this->ID );
               
$UserSettings->set( 'notify_list_lost_subscriber', param( 'edited_user_notify_list_lost_subscriber', 'integer', 0 ), $this->ID );

               
// Emails limit per day
               
param_integer_range( 'edited_user_notification_email_limit', 0, 999, T_('Notificaiton email limit must be between %d and %d.'), ! $is_api_request );
               
$UserSettings->set( 'notification_email_limit', param( 'edited_user_notification_email_limit', 'integer', 0 ), $this->ID );
               
param_integer_range( 'edited_user_newsletter_limit', 0, 999, T_('List limit must be between %d and %d.'), ! $is_api_request );
               
$UserSettings->set( 'newsletter_limit', param( 'edited_user_newsletter_limit', 'integer', 0 ), $this->ID );

               
// If notifications are temporarily disabled, no subscription options should be lost when user saves.
                // When notification are turned back on, the subscription will resume as they were before.
               
if( $notifications_mode != 'off' )
                {
                   
/**
                     * Update the subscriptions:
                     */
                   
$subs_blog_IDs = param( 'subs_blog_IDs', 'string', ! $is_api_request ? true : NULL );
                   
$subs_item_IDs = param( 'subs_item_IDs', 'string', ! $is_api_request ? true : NULL );

                   
// Work the blogs:
                   
$BlogCache = & get_BlogCache();
                   
$subscription_values = array();
                   
$opt_out_values = array();
                   
$unsubscribed = array();
                    if(
$subs_blog_IDs )
                    {
                       
$subs_blog_IDs = explode( ',', $subs_blog_IDs );

                        foreach(
$subs_blog_IDs as $loop_blog_ID )
                        {
                           
// Make sure no dirty hack is coming in here:
                           
$loop_blog_ID = intval( $loop_blog_ID );

                           
// Get checkbox values:
                           
$sub_items    = param( 'sub_items_'.$loop_blog_ID,    'integer', 0 );
                           
$sub_comments = param( 'sub_comments_'.$loop_blog_ID, 'integer', 0 );
                           
$sub_items_mod = param( 'sub_items_mod_'.$loop_blog_ID, 'integer', 0 );

                            if(
$sub_items || $sub_comments || $sub_items_mod )
                            {    
// We have a subscription for this blog
                               
$subscription_values[] = "( $loop_blog_ID, $this->ID, $sub_items, $sub_items_mod, $sub_comments )";
                            }
                            else
                            {    
// No subscription here:

                                // Check if opt-out and user is a member
                               
$Blog = & $BlogCache->get_by_ID( $loop_blog_ID );

                                if(
$Blog->get( 'advanced_perms' )
                                        && ( (
$Blog->get_setting( 'allow_subscriptions' ) && $Blog->get_setting( 'opt_out_subscription' ) ) ||
                                             (
$Blog->get_setting( 'allow_item_mod_subscriptions' ) && $Blog->get_setting( 'opt_out_item_mod_subscription' ) ) ||
                                             (
$Blog->get_setting( 'allow_comment_subscriptions' ) && $Blog->get_setting( 'opt_out_comment_subscription' ) ) )
                                        &&
$this->check_perm( 'blog_ismember', 'view', false, $loop_blog_ID ) )
                                {
                                   
$opt_item = $Blog->get_setting( 'allow_subscriptions' ) && $Blog->get_setting( 'opt_out_subscription' ) ? $sub_items : 0;
                                   
$opt_item_mod = $Blog->get_setting( 'allow_item_mod_subscriptions' ) && $Blog->get_setting( 'opt_out_item_mod_subscription' ) ? $sub_items : 0;
                                   
$opt_comment = $Blog->get_setting( 'allow_comment_subscriptions' ) && $Blog->get_setting( 'opt_out_comment_subscription' ) ? $sub_comments : 0;
                                   
$opt_out_values[] = "( $loop_blog_ID, $this->ID, $opt_item, $opt_item_mod, $opt_comment )";
                                }
                                else
                                {
                                   
$unsubscribed[] = $loop_blog_ID;
                                }
                            }
                        }
                    }

                   
// Note: we do not check if subscriptions are allowed here, but we check at the time we're about to send something
                   
if( count( $subscription_values ) )
                    {    
// We need to record values:
                       
$DB->query( 'REPLACE INTO T_subscriptions( sub_coll_ID, sub_user_ID, sub_items, sub_items_mod, sub_comments )
                                                    VALUES '
.implode( ', ', $subscription_values ) );
                    }

                    if(
count( $opt_out_values ) )
                    {
                       
$DB->query( 'REPLACE INTO T_subscriptions( sub_coll_ID, sub_user_ID, sub_items, sub_items_mod, sub_comments )
                                                    VALUES '
.implode( ', ', $opt_out_values ) );
                    }

                    if(
count( $unsubscribed ) )
                    {    
// We need to make sure some values are cleared:
                       
$DB->query( 'DELETE FROM T_subscriptions
                                                    WHERE sub_user_ID = '
.$this->ID.'
                                                        AND sub_coll_ID IN ('
.implode( ', ', $unsubscribed ).')' );
                    }

                   
// Individual post subscriptions
                   
if( !empty( $subs_item_IDs ) )
                    {
// user was subscribed to at least one post update notification
                       
$ItemCache = & get_ItemCache();
                       
$subs_item_IDs = explode( ',', $subs_item_IDs );
                       
$opt_out_values = array();
                       
$unsubscribed = array();

                       
// get list of aggregated items:
                       
$sub_group_IDs = param( 'item_grp_sub', 'array:string' );
                       
$sub_group_IDs = implode( ',', $sub_group_IDs );
                       
$sub_group_IDs = explode( ',', $sub_group_IDs );

                        foreach(
$subs_item_IDs as $loop_item_ID )
                        {
                           
$isub_comments = param( 'items_subs_'.$loop_item_ID, 'integer', 0 );

                            if( !
param( 'item_sub_'.$loop_item_ID, 'integer', 0 ) && !in_array( $loop_item_ID, $sub_group_IDs ) )
                            {
// user wants to unsubscribe from this post notifications
                               
$Item = $ItemCache->get_by_ID( $loop_item_ID );
                               
$blog_ID = $Item->get_blog_ID();
                               
$Blog = $BlogCache->get_by_ID( $blog_ID );

                                if(
$Blog->get( 'advanced_perms' )
                                        &&
$Blog->get_setting( 'allow_item_subscriptions' )
                                        &&
$Blog->get_setting( 'opt_out_item_subscription' )
                                        &&
$this->check_perm( 'blog_ismember', 'view', false, $blog_ID ) )
                                {
                                   
$opt_out_values[] = "( $loop_item_ID, $this->ID, $isub_comments )";
                                }
                                else
                                {
                                   
$unsubscribed[] = $loop_item_ID;
                                }

                            }
                        }
                        if( !empty(
$opt_out_values ) )
                        {
                           
$DB->query( 'REPLACE INTO T_items__subscriptions( isub_item_ID, isub_user_ID, isub_comments )
                                                    VALUES '
.implode( ', ', $opt_out_values ) );
                        }

                        if( !empty(
$unsubscribed ) )
                        {
// unsubscribe list is not empty, delete not wanted subscriptions
                           
$DB->query( 'DELETE FROM T_items__subscriptions
                                                    WHERE isub_user_ID = '
.$this->ID.'
                                                        AND isub_item_ID IN ('
.implode( ', ', $unsubscribed ).')' );
                        }
                    }
                }
            }
        }

       
// ******* Advanced form ******* //
       
$is_advanced_form = param( 'advanced_form', 'boolean', false );

        if(
$is_advanced_form )
        {
           
/*
             * We currently support only one backoffice skin, so we don't need a system for selecting the backoffice skin.
            $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_page_size = param( 'edited_user_results_page_size', 'integer', NULL );
            if( isset(
$edited_user_results_page_size ) )
            {
               
$UserSettings->set( 'results_per_page', $edited_user_results_page_size, $this->ID );
            }
        }

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

        return !
param_errors_detected();
    }


   
/**
     * Perform various checks on user login
     *
     * @return string User login
     */
   
function check_login()
    {
        global
$current_User;
        global
$is_api_request;

       
$has_full_access = check_user_perm( 'users', 'edit' );
       
$has_moderate_access = $current_User->can_moderate_user( $this->ID );

       
// TRUE when we create new user:
       
$is_new_user = ( $this->ID == 0 );

       
$edited_user_login = param( 'edited_user_login', 'string', NULL );
        if( !
$is_api_request || ( $is_api_request && ( isset( $edited_user_login ) || $is_new_user ) ) )
        {
// login specifically included in API request
           
if( empty( $edited_user_login ) )
            {    
// Empty login
               
param_error( 'edited_user_login', T_('Please enter your login.') );
            }
            if(
param_check_valid_login( 'edited_user_login' ) )
            {    
// If login is valid
               
global $reserved_logins;
                if(
$edited_user_login != $this->get( 'login' ) &&
                    ! empty(
$reserved_logins ) &&
                   
in_array( $edited_user_login, $reserved_logins ) &&
                    ( !
is_logged_in() || ! $has_full_access ) )
                {    
// If login has been changed and new entered login is reserved and current User cannot use this:
                   
param_error( 'edited_user_login', T_('You cannot use this login because it is reserved.') );
                }
            }
        }
        else
        {
           
set_param( 'edited_user_login', $this->login );
           
$edited_user_login = $this->login;
        }

       
$UserCache = & get_UserCache();
       
$UserLogin = $UserCache->get_by_login( $edited_user_login );
        if(
$UserLogin && $UserLogin->ID != $this->ID )
        {    
// The login is already registered
           
$login_error_message = T_( 'This login already exists.' );
            if(
$has_full_access )
            {
               
$login_error_message = sprintf( T_( 'This login &laquo;%s&raquo; already exists. Do you want to <a %s>edit the existing user</a>?' ),
                   
$edited_user_login,
                   
'href="'.get_user_settings_url( 'profile', $UserLogin->ID ).'"' );
            }
           
param_error( 'edited_user_login', $login_error_message );
        }

        if( !
param_has_error( 'edited_user_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, 'utf8_strtolower' );
        }

        return
$edited_user_login;
    }


   
/**
     * Get a param
     *
     * @param string the parameter
     */
   
function get( $parname )
    {
        if(
$this->check_status( 'is_closed' ) && ( !is_admin_page() ) && ( $parname != 'login' ) && ( $parname != 'status' ) )
        {
// if this account is closed and we are not in backoffice, don't return other information then login or status
           
return NULL;
        }

        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();

            case
'num_comments':
                return
$this->get_num_comments();

            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()
    {
        if(
$this->check_status( 'is_closed' ) && ( !is_admin_page() ) )
        {
// don't return closed accounts information except login name
           
return $this->login;
        }

        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', // avatar | preferredname
               
'link_rel'     => '',
               
'link_class'   => '',
               
'thumb_size'   => 'crop-top-32x32',
               
'thumb_class'  => '',
               
'thumb_zoom'   => false,
               
'thumb_overlay'=> '',
               
'tag_size'     => NULL,
            ),
$params );

        if(
$params['link_text'] == 'avatar' )
        {
           
$r = $this->get_avatar_imgtag( $params['thumb_size'], $params['thumb_class'], '', $params['thumb_zoom'], $params['thumb_overlay'], '', $params['tag_size'] );
        }
        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( utf8_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
     *
     * @return string
     */
   
function get_preferred_name()
    {
        if(
$this->check_status( 'is_closed' ) && ( !is_admin_page() ) )
        {
// don't return closed accounts information except login name
           
return $this->login;
        }

        if( !empty(
$this->nickname ) )
        {    
// Nickname
           
return $this->get( 'nickname' );
        }
        elseif( !empty(
$this->firstname ) || !empty( $this->lastname ) )
        {    
// First name
           
return $this->get('fullname');
        }
        else
        {    
// Login
           
return $this->get( 'login' );
        }
    }


   
/**
     * Get user's login with gender color
     *
     * @param array Params
     * @return string User's preferred name with gender color if this available
     */
   
function get_colored_login( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'mask'         => '$login$', // $avatar$ $login$
               
'login_format' => 'htmlbody',
               
'avatar_size'  => 'crop-top-15x15',
               
'login_text'   => 'login', // name | login
               
'use_style'    => false, // true - to use attr "style", e.g. on email templates
               
'extra_class'  => '',
               
'protocol'     => '', // Protocol is used for gravatar, example: 'http:' or 'https:'
           
), $params );

       
$avatar = '';
       
$login = '';
       
$class = '';

        if(
strpos( $params['mask'], '$login$' ) !== false )
        {
// Display login or preferred name
           
if( $params['login_text'] == 'name' )
            {
// Use nickname or fullname
               
$login = $this->get_username( $params['login_format'] );
            }
            else
            {
// Use a login
               
$login = $this->dget( 'login', $params['login_format'] );
            }

           
// Add class "login" to detect logins by js plugins
           
$class = ( $login == $this->login ? 'login ' : '' );
        }

        if(
strpos( $params['mask'], '$avatar$' ) !== false )
        {
// Display avatar:
           
$avatar = $this->get_avatar_imgtag( $params['avatar_size'], '', '', false, '', '', NULL, $params['protocol'] );
        }

       
$mask = array( '$login$', '$avatar$' );
       
$data = array( $login, $avatar );

       
$gender_class = $this->get_gender_class();
       
$extra_class = '';
        if( !empty(
$params['extra_class'] ) )
        {
            if( !
is_array( $params['extra_class']))
            {
               
$params['extra_class'] = array($params['extra_class']);
            }
           
$extra_class = ' '.implode( ' ', $params['extra_class'] );
        }
       
$attr_style = '';
        if(
$params['use_style'] )
        {
// Use "style"
           
$attr_style = emailskin_style( '.user+.'.str_replace( ' ', '.', $gender_class ).str_replace( ' ', '+.user.', $extra_class ) );
        }

        return
'<span class="'.trim( $class.' '.$gender_class.$extra_class ).'"'.$attr_style.'>'.str_replace( $mask, $data, $params['mask'] ).'</span>';
    }


   
/**
     * Get User identity link, which is a composite of user avatar and login, both point to the specific user profile tab.
     *
     * @return string User avatar and login if the identity link is not available, the identity link otherwise.
     */
   
function get_identity_link( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'link_text'      => 'avatar_name', // auto| avatar_name | avatar_login | only_avatar | name | login | nickname | firstname | lastname | fullname | preferredname
               
'link_class'     => '', // Class for <a href=""
               
'thumb_size'     => 'crop-top-15x15',
               
'thumb_class'    => 'avatar_before_login',
               
'thumb_zoomable' => false,
               
'login_mask'     => '', // example: 'text $login$ text'  TODO: replace with template
               
'login_class'    => 'identity_link_username',  // Not used if login_mask is used
               
'display_bubbletip' => true,
               
'nowrap'         => true,
               
'user_tab'       => 'profile',
               
'use_style'      => false, // true - to use attr "style" instead of "class", e.g. on email templates
               
'blog_ID'        => NULL,
               
'protocol'       => '', // Protocol is used for gravatar, example: 'http:' or 'https:'
           
), $params );

       
$identity_url = get_user_identity_url( $this->ID, $params['user_tab'], $params['blog_ID'] );

       
$attr_bubbletip = '';
        if(
$params['display_bubbletip'] )
        {    
// Set attribute to initialize a bubbletip
           
$attr_bubbletip = ' rel="bubbletip_user_'.$this->ID.'"';
        }

       
$avatar_tag = '';
        if(
strpos( $params['link_text'], 'avatar' ) !== false )
        {
// Avatar must be displayed in this link
           
$avatar_tag = $this->get_avatar_imgtag( $params['thumb_size'], $params['thumb_class'], '', $params['thumb_zoomable'], '', '', NULL, $params['protocol'] );
            if(
$params['thumb_zoomable'] )
            {
// User avatar is zoomable
                // Add tag param to init bubbletip
               
$avatar_tag = str_replace( '<img ', '<img rel="bubbletip_user_'.$this->ID.'"', $avatar_tag );
                return
$avatar_tag; // We should exit here, to avoid the double adding of tag <a>
           
}
        }

       
$linktext = '';
       
$class = $params['link_class'];

        if(
$params['link_text'] != 'only_avatar' )
        {
// Display a login, nickname, firstname, lastname, fullname or preferredname
           
switch( $params['link_text'] )
            {
                case
'auto':        // TODO this should be the real auto
               
case 'login':
                case
'avatar_login':
                   
$linktext = $this->get_username();  // TODO: auto has been hardcoded into this ! :(
                   
break;
                case
'force_login':        // TODO: we should NOT need a "force" if we correctly use auto the rest of the time!
               
case 'avatar_force_login':
                   
$linktext = $this->get( 'login' );
                    break;
                case
'nickname':
                   
$linktext = $this->nickname;
                    break;
                case
'firstname':
                   
$linktext = $this->firstname;
                    break;
                case
'lastname':
                   
$linktext = $this->lastname;
                    break;
                case
'fullname':
                   
$linktext = $this->firstname.' '.$this->lastname;
                    break;
                case
'preferredname':
                   
$linktext = $this->get_preferred_name();
                    break;
               
// default: 'avatar_name' | 'avatar' | 'name'
           
}
           
$linktext = trim( $linktext );
            if( empty(
$linktext ) )
            {
// Use a login or preferred name by default
               
$linktext = $this->get_username();
            }

           
// Add class "login" to detect logins by js plugins
           
$class .= ( $linktext == $this->login ? ' login' : '' );
           
            if( !empty(
$params['login_mask']) )
            {
// Apply login mask
               
$linktext = str_replace( '$login$', $linktext, $params['login_mask'] );
            }
            elseif( !empty(
$params['login_class']) )
            {
               
$linktext = '<span class="'.$params['login_class'].'">'.$linktext.'</span>';
            }
        }
// END Not for "only avatar"

       
$gender_class = $this->get_gender_class();
       
$attr_style = '';
        if(
$params['use_style'] )
        {
// Use "style" (e-g email template)
           
$attr_style = emailskin_style( '.user+.'.str_replace( ' ', '.', $gender_class ) );
        }
       
$attr_style = ' class="'.trim( $class.' '.$gender_class.( $params['nowrap'] ? ' nowrap' : '' ) ).'"'.$attr_style;

        if( empty(
$identity_url ) )
        {
            return
'<span'.$attr_style.$attr_bubbletip.'>'.$avatar_tag.$linktext.'</span>';
        }

       
$link_title = T_( 'Show the user profile' );
        return
'<a href="'.$identity_url.'" title="'.$link_title.'"'.$attr_style.$attr_bubbletip.'>'.$avatar_tag.$linktext.'</a>';
    }


   
/**
     * Get Regional object
     *
     * @param string Object name (Country, Region, Subregion, City)
     * @param string ID name
     * @return object
     */
   
function & get_Regional( $Object, $ID )
    {
        if(
$this->check_status( 'is_closed' ) && ( !is_admin_page() ) )
        {
// don't return closed accounts regional information to front office
           
$r = false;
            return
$r;
        }

        if(
is_null($this->$Object) && !empty($this->$ID ) )
        {
           
$Cache = call_user_func( 'get_'.$Object.'Cache' );
           
$this->$Object = & $Cache->get_by_ID( $this->$ID, false );
        }

        return
$this->$Object;
    }


   
/**
     * Get Regional name
     *
     * @param string Object name (Country, Region, Subregion, City)
     * @param string ID name
     * @return string Name of Country, Region, Subregion or City
     */
   
function get_regional_name( $Object, $ID )
    {
        if(
$this->get_Regional( $Object, $ID ) )
        {    
// We have an regional object:
           
return $this->$Object->name;
        }

        return
'';
    }


   
/**
     * Get Country object
     */
   
function & get_Country()
    {
        return
$this->get_Regional( 'Country', 'ctry_ID' );
    }


   
/**
     * Get country name
     */
   
function get_country_name()
    {
        if( empty(
$this->ctry_ID ) )
        {
            return;
        }

       
load_class( 'regional/model/_country.class.php', 'Country' );
        return
$this->get_regional_name( 'Country', 'ctry_ID' );
    }

   
/**
     * Get region name
     */
   
function get_region_name()
    {
        if( empty(
$this->rgn_ID ) )
        {
            return;
        }

       
load_class( 'regional/model/_region.class.php', 'Region' );
        return
$this->get_regional_name( 'Region', 'rgn_ID' );
    }

   
/**
     * Get subregion name
     */
   
function get_subregion_name()
    {
        if( empty(
$this->subrg_ID ) )
        {
            return;
        }

       
load_class( 'regional/model/_subregion.class.php', 'Subregion' );
        return
$this->get_regional_name( 'Subregion', 'subrg_ID' );
    }

   
/**
     * Get city name
     *
     * @param boolean TRUE - show postcode
     * @return string city name
     */
   
function get_city_name( $show_postcode = true )
    {
        if( empty(
$this->city_ID ) )
        {
            return
'';
        }

       
load_class( 'regional/model/_city.class.php', 'City' );
       
$city = $this->get_regional_name( 'City', 'city_ID' );
        if(
$this->City && $show_postcode )
        {    
// Get postcode
           
$city .= ' ('.$this->City->postcode.')';
        }
        return
$city;
    }


   
/**
     * Get the number of blogs owned by this user
     *
     * @return integer
     */
   
function get_num_blogs()
    {
        global
$DB;

        return
$DB->get_var( 'SELECT count(*)
                                FROM T_blogs
                                WHERE blog_owner_user_ID = '
.$this->ID );
    }


   
/**
     * Get the number of posts for the user.
     *
     * @param string Posts status
     * @return integer
     */
   
function get_num_posts( $status = '' )
    {
        global
$DB;
        global
$collections_Module;

        if( isset(
$collections_Module ) && is_null( $this->_num_posts ) )
        {
           
$SQL = new SQL( 'Get the number of posts for the user' );
           
$SQL->SELECT( 'post_status, COUNT(*)' );
           
$SQL->FROM( 'T_items__item' );
           
$SQL->WHERE( 'post_creator_user_ID = '.$this->ID );
           
$SQL->GROUP_BY( 'post_status' );
           
$this->_num_posts = $DB->get_assoc( $SQL->get() );

           
// Calc number of posts with all statuses
           
$total_num_posts = 0;
            foreach(
$this->_num_posts as $status_num_posts )
            {
               
$total_num_posts += $status_num_posts;
            }
           
$this->_num_posts[''] = $total_num_posts;
        }

        return !empty(
$this->_num_posts[ $status ] ) ? $this->_num_posts[ $status ] : 0;
    }


   
/**
     * Get the number of comments for the user.
     *
     * @param string Comments status
     * @param boolean Count already recycled comments
     * @return integer
     */
   
function get_num_comments( $status = '', $count_recycled = false )
    {
        global
$DB;
        global
$collections_Module;

        if( isset(
$collections_Module ) && is_null( $this->_num_comments ) )
        {
           
$SQL = new SQL( 'Get the number of comments for the user' );
           
$SQL->SELECT( 'comment_status, COUNT(*)' );
           
$SQL->FROM( 'T_comments' );
           
$SQL->WHERE( 'comment_author_user_ID = '.$this->ID );
           
$SQL->WHERE_and( 'comment_type IN ( "comment", "trackback", "pingback", "webmentions" )' );
           
$SQL->GROUP_BY( 'comment_status' );
           
$this->_num_comments = $DB->get_assoc( $SQL );

           
// Calc number of comments with all statuses
           
$total_num_comments = 0;
            foreach(
$this->_num_comments as $curr_status => $status_num_comments )
            {
                if(
$count_recycled || ( $curr_status != 'trash' ) )
                {
                   
$total_num_comments += $status_num_comments;
                }
            }
           
$this->_num_comments[''] = $total_num_comments;
        }

        return !empty(
$this->_num_comments[ $status ] ) ? $this->_num_comments[ $status ] : 0;
    }


   
/**
     * Get the number of files for the user.
     *
     * @param string File type: 'image', 'audio', 'other'
     * @return integer
     */
   
function get_num_files( $type = NULL )
    {
        global
$DB;

        if(
is_null( $this->_num_files ) )
        {
           
$files_SQL = new SQL();
           
$files_SQL->SELECT( 'file_type, COUNT( file_ID ) AS cnt' );
           
$files_SQL->FROM( 'T_files' );
           
$files_SQL->WHERE( 'file_creator_user_ID = '.$this->ID );
           
$files_SQL->GROUP_BY( 'file_type' );
           
$this->_num_files = $DB->get_assoc( $files_SQL->get() );
        }

        return ! empty(
$this->_num_files[ $type ] ) ? $this->_num_files[ $type ] : 0;
    }


   
/*
     * Get the total size of files uploaded by the user
     *
     * @param string File type: 'image', 'video', 'audio', 'other'; NULL - for all file types
     * @return integer total size in bytes
     */
   
function get_total_upload( $type = NULL )
    {
        if( empty(
$this->ID ) )
        {    
// User must be saved in DB:
           
return 0;
        }

       
$FileCache = & get_FileCache();
       
$FileCache->clear();
       
$files_sql_where = 'file_creator_user_ID = '.$this->ID;
        if(
$type !== NULL )
        {    
// Restrict files by type:
           
global $DB;
           
$files_sql_where .= ' AND file_type = '.$DB->quote( $type );
        }
       
$FileCache->load_where( $files_sql_where );

       
$total_upload_size = 0;
        foreach(
$FileCache->cache as $user_File )
        {
           
$total_upload_size += $user_File->get_size();
        }

        return
$total_upload_size;
    }


   
/**
     * Get the number of user sessions
     *
     * @param boolean set true to return the number of sessions as a link to the user sessions list
     * @return integer|string number of sessions or link to user sessions where the link text is the number of sessions
     */
   
function get_num_sessions( $link_sessions = false )
    {
        global
$DB;

       
$num_sessions = $DB->get_var( 'SELECT count( sess_ID )
                                            FROM T_sessions
                                            WHERE sess_user_ID = '
.$this->ID );

        if(
$link_sessions && ( $num_sessions > 0 ) )
        {
            return
$num_sessions.' - <a href="?ctrl=user&amp;user_ID='.$this->ID.'&amp;user_tab=sessions" class="'.button_class().' middle" title="'.format_to_output( T_('Go to user activity'), 'htmlattr' ).'">'.get_icon( 'magnifier', 'imgtag', array( 'title' => T_('Go to user activity') ) ).'</a>';
        }

        return
$num_sessions;
    }


   
/**
     * Get the number of user messages
     *
     * @param string Type ( sent | received )
     * @return integer the number of requested type of messages
     */
   
function get_num_messages( $type = 'sent' )
    {
        global
$DB;

        if(
$type == 'received' )
        {    
// Get a count of messages received
           
$SQL = new SQL( 'Get a count of messages received' );
           
$SQL->SELECT( 'COUNT( msg_ID )' );
           
$SQL->FROM( 'T_messaging__threadstatus' );
           
$SQL->FROM_add( 'LEFT JOIN T_messaging__message ON tsta_thread_ID = msg_thread_ID' );
           
$SQL->WHERE( 'tsta_user_ID = '.$DB->quote( $this->ID ) );
           
$SQL->WHERE_and( 'msg_author_user_ID != '.$DB->quote( $this->ID ) );
        }
        else
        {    
// Get a count of messages sent
           
$SQL = new SQL( 'Get a count of messages sent' );
           
$SQL->SELECT( 'COUNT( msg_ID )' );
           
$SQL->FROM( 'T_messaging__message' );
           
$SQL->WHERE( 'msg_author_user_ID = '.$DB->quote( $this->ID ) );
        }

        return
$DB->get_var( $SQL );
    }


   
/**
     * Get the number of other users posts which were edited by this user
     *
     * @return integer the number of edited posts
     */
   
function get_num_edited_posts()
    {
        global
$DB;

        return
$DB->get_var( 'SELECT COUNT( DISTINCT( post_ID ) )
                                    FROM T_items__item
                                    INNER JOIN T_items__version ON post_ID = iver_itm_ID
                                    WHERE post_creator_user_ID <> '
.$this->ID.' AND
                                        ( iver_edit_user_ID = '
.$this->ID.' OR post_lastedit_user_ID = '.$this->ID.' )' );
    }


   
/**
     * Get the number of polls owned by this user
     *
     * @return integer the number of owned polls
     */
   
function get_num_polls()
    {
        global
$DB;

        if( empty(
$this->ID ) )
        {
            return
0;
        }

       
$SQL = new SQL( 'Get polls owned by user #'.$this->ID );
       
$SQL->SELECT( 'COUNT( pqst_ID )' );
       
$SQL->FROM( 'T_polls__question' );
       
$SQL->WHERE( 'pqst_owner_user_ID = '.$this->ID );

        return
$DB->get_var( $SQL );
    }


   
/**
     * Get a number of emails sent to this User
     *
     * @return integer
     */
   
function get_num_sent_emails()
    {
        global
$DB;

        if( empty(
$this->ID ) )
        {
            return
0;
        }

       
$SQL = new SQL( 'Get a number of emails sent to the User #'.$this->ID );
       
$SQL->SELECT( 'COUNT( emlog_ID )' );
       
$SQL->FROM( 'T_email__log' );
       
$SQL->WHERE( 'emlog_user_ID = '.$this->ID );

        return
intval( $DB->get_var( $SQL ) );
    }


   
/**
     * Delete all emails sent to the user
     *
     * @return boolean True on success
     */
   
function delete_sent_emails()
    {
        global
$DB;

        return
$DB->query( 'DELETE
             FROM T_email__log
            WHERE emlog_user_ID = '
.$this->ID );
    }


   
/**
     * Get a number of email returns from this User's email address
     *
     * @return integer
     */
   
function get_num_email_returns()
    {
        global
$DB;

        if( empty(
$this->ID ) )
        {
            return
0;
        }

       
$SQL = new SQL( 'Get a number of email returns from email address of the User #'.$this->ID );
       
$SQL->SELECT( 'COUNT( emret_ID )' );
       
$SQL->FROM( 'T_email__returns' );
       
$SQL->WHERE( 'emret_address = '.$DB->quote( $this->get( 'email' ) ) );

        return
intval( $DB->get_var( $SQL ) );
    }


   
/**
     * Delete all email returns from the user's email address
     *
     * @return boolean True on success
     */
   
function delete_email_returns()
    {
        global
$DB;

        return
$DB->query( 'DELETE
             FROM T_email__returns
            WHERE emret_address = '
.$DB->quote( $this->get( 'email' ) ) );
    }


   
/**
     * 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.$this->get_media_subpath() );

        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( !
evo_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
            {
// add note:
               
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;
        global
$Collection, $Blog;

        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;
        }

        if( ! empty(
$Blog ) && ! is_admin_page() )
        {    
// We are currently looking at a collection on front-office.
            // We are going to consider (for now) that we want the users and their files
            // to appear as being part of that collection.
           
return $Blog->get_local_media_url().$this->get_media_subpath();
        }

       
// System media url:
       
return $media_url.$this->get_media_subpath();
    }


   
/**
     * Get user media directory subpath, e.g. users/{login}/ or users/usr_{user ID}/
     */
   
function get_media_subpath()
    {
        if(
is_valid_login( $this->login, true ) === true )
        {    
// Valid ASCII login, use it as is
           
return 'users/'.$this->login.'/';
        }
        else
        {    
// Non-ASCII login
           
return 'users/usr_'.$this->ID.'/';
        }
    }


   
/**
     * 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
     *
     * @param integer|NULL Blog ID or NULL to use current blog
     * @param boolean TRUE to force use URL even when current user in not logged in
     * @return string
     */
   
function get_userpage_url( $blog_ID = NULL, $force_url = false )
    {
        global
$Settings;

        if( ! empty(
$blog_ID ) )
        {
// Use blog from param by ID
           
$BlogCache = & get_BlogCache();
           
$current_Blog = & $BlogCache->get_by_ID( $blog_ID, false, false );
        }

        if( empty(
$current_Blog ) )
        {
// Use current blog
           
global $Collection, $Blog;
           
$current_Blog = & $Blog;
        }

        if( empty(
$current_Blog ) || empty( $Settings ) )
        {
// Wrong request
           
return NULL;
        }

        if(
$force_url || is_logged_in() )
        {
// If current User is logged in
           
if( $Settings->get( 'user_url_loggedin' ) == 'url' && $this->get_field_url( true ) != '' )
            {
// Use website url if it is defined and setting enables this
               
return $this->get_field_url( true );
            }
            else
            {
// Use an user page if url is not defined
               
return $current_Blog->get( 'userurl', array( 'user_ID' => $this->ID, 'user_login' => $this->login ) );
            }
        }
        else
        {
// For anonymous users
           
$this->get_Group();
            if(
$this->Group->level >= $Settings->get( 'allow_anonymous_user_level_min' ) &&
               
$this->Group->level <= $Settings->get( 'allow_anonymous_user_level_max' ) )
            {
// Check if anonymous users have an access to view this user
               
if( $Settings->get( 'user_url_anonymous' ) == 'url' && $this->get_field_url( true ) != '' )
                {
// Use website url if it is defined and setting enables this
                   
return $this->get_field_url( true );
                }
                elseif(
$Settings->get( 'allow_anonymous_user_profiles' ) )
                {
// Use an user page if url is not defined and this is enabled by setting for anonymous users
                   
return $current_Blog->get( 'userurl', array( 'user_ID' => $this->ID, 'user_login' => $this->login ) );
                }
            }
        }

       
// return NULL if user page url is not allowed
       
return NULL;
    }

   
/**
     * Get url visits url
     *
     * @param integer Collection ID
     * @return string
     */
   
function get_visits_url( $blog_ID = NULL )
    {
        if( ! empty(
$blog_ID ) )
        {    
// Use collection from param by ID:
           
$BlogCache = & get_BlogCache();
           
$url_Blog = & $BlogCache->get_by_ID( $blog_ID, false, false );
        }

        if( empty(
$url_Blog ) )
        {    
// Use current collection:
           
global $Collection, $Blog;
           
$url_Blog = & $Blog;
        }

        if( empty(
$url_Blog ) )
        {    
// Wrong request:
           
return NULL;
        }

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


   
/**
     * Get url from defined userfields
     *
     * @param boolean TRUE if we want get a value from url fields of the table T_users__fields, FALSE - get from $this->url
     * @return string Url
     */
   
function get_field_url( $from_extra_fields = false )
    {
        global
$DB;

        if(
$from_extra_fields )
        {    
// Get value from DB or from cache
           
if( isset( $this->field_url ) )
            {    
// Get url from variable already defined in first calling of this method
               
return $this->field_url;
            }
            else
            {    
// Get value from DB
               
$this->field_url = (string)$DB->get_var( '
                    SELECT uf_varchar
                        FROM T_users__fields
                            LEFT JOIN T_users__fielddefs ON uf_ufdf_ID = ufdf_ID
                            LEFT JOIN T_users__fieldgroups ON ufdf_ufgp_ID = ufgp_ID
                    WHERE uf_user_ID = '
.$this->ID.'
                        AND ufdf_type = "url"
                    ORDER BY ufgp_order, ufdf_order, uf_ID
                    LIMIT 1'
);
                return
$this->field_url;
            }
        }
        else
        {    
// Get value from $this->url (T_users)
           
return $this->url;
        }
    }


   
/**
     * Get link from defined userfields
     *
     * @param array Template params
     * @return string Link
     */
   
function get_field_link( $params = array() )
    {
       
$params = array_merge( array(
               
'target' => '_blank',
               
'rel'    => 'nofollow',
               
'class'  => '',
            ),
$params );

       
// Add style class to break long urls:
       
$params['class'] = trim( $params['class'].' linebreak' );

       
$link = '<a href="'.$this->get_field_url().'"'.get_field_attribs_as_string( $params, false ).'>'.$this->get_field_url().'</a>';

        return
$link;
    }


   
/**
     * 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 )
    {
        if( ( ! isset(
$this->significant_changed_values[ $parname ] ) ) && ( ( $old_value = $this->get( $parname ) ) != $parvalue )
            &&
in_array( $parname, array( 'login', 'grp_ID', 'nickname', 'firstname', 'lastname', 'gender', 'ctry_ID', 'rgn_ID', 'subrg_ID', 'city_ID' ) ) )
        {
// Save previous value of significant changes for later use in send_account_changed_notifications()
           
$this->significant_changed_values[ $parname ] = $old_value;
        }

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

            case
'source':
               
// Limit source with 30 char because of this DB field max length:
               
return $this->set_param( $parname, 'string', utf8_substr( $parvalue, 0, 30 ), $make_null );

            case
'status':
               
// We need to set a reminder here to later check if the new status is allowed at dbinsert or dbupdate time ( $this->restrict_status( true ) )
                // We cannot check immediately because we may be setting the status before having set a main cat_ID -> a collection ID to check the status possibilities
                // Save previous status as a reminder (it can be useful to compare later. The Comment class uses this).
               
$this->previous_status = $this->get( 'status' );
                return
parent::set( 'status', $parvalue, $make_null );

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


   
/**
     * Set the encoded password and a new password salt from a raw password
     *
     * @param string the raw password
     */
   
function set_password( $raw_password )
    {
       
// Use first password driver from config array for new password updating:
       
$PasswordDriver = get_PasswordDriver();

       
// Save temporarily current pass driver to know if it has been chaged:
       
$this->previous_pass_driver = $this->get( 'pass_driver' );

       
$this->set( 'pass', $PasswordDriver->hash( $raw_password ) );
       
$this->set( 'salt', $PasswordDriver->get_last_generated_salt() );
       
$this->set( 'pass_driver', $PasswordDriver->get_code() );
    }


   
/**
     * 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( 'user_created_datetime', '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 not active 'emaichanged' status here.
     *
     * @param string email address to set for the User
     * @param boolean Set TRUE if changing of account status is required
     * @return boolean true, if set; false if not changed
     */
   
function set_email( $email, $change_status = true )
    {
        global
$Settings;

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

        if(
$change_status )
        {
// Change user status to 'emailchanged' (if email has changed and Settings are available, which they are not during install):
           
if( $r && ( $this->ID != 0 ) && isset($Settings) && $Settings->get('newusers_revalidate_emailchg') && ( $this->check_status( 'is_validated' ) ) )
            {
// Deactivate the account, because the changed email has not been verified yet, but the user account is active:
               
$this->set( 'status', 'emailchanged' );
            }
        }

        if(
preg_match( '#@(.+)#i', $email, $ematch ) )
        {    
// Set email domain ID
           
global $DB;

           
$email_domain = $ematch[1];

           
$SQL = new SQL( 'Get email domain' );
           
$SQL->SELECT( 'dom_ID' );
           
$SQL->FROM( 'T_basedomains' );
           
$SQL->WHERE_and( 'dom_name = '.$DB->quote( $email_domain ) );

           
$dom_ID = $DB->get_var( $SQL );
            if( !
$dom_ID )
            {    
// The email domains doesn't exist yet, Insert new record
               
$DB->query( 'INSERT INTO T_basedomains ( dom_type, dom_name )
                    VALUES ( \'email\', '
.$DB->quote( $email_domain ).' )' );
               
$dom_ID = $DB->insert_id;
            }

           
$this->set( 'email_dom_ID', (int) $dom_ID );
        }

        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(
$this->grp_ID !== $Group->ID )
        {
           
$this->Group = & $Group;
           
$this->set( 'grp_ID', $Group->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->grp_ID);
        }
        return
$this->Group;
    }


 
/**
     * Check password
     *
     * @param string Password
     * @param boolean Is the password parameter already hashed?
     * @return boolean
     */
   
function check_password( $pass, $pass_is_hashed = false )
    {
       
// Get password driver for this user:
       
$user_PasswordDriver = $this->get_PasswordDriver();

        if( !
$user_PasswordDriver )
        {    
// Password driver cannot be detected for this user, so it cannot be checked:
           
return false;
        }

       
// Check the requested password against the user's password hash:
       
return $user_PasswordDriver->check( $pass, $this->salt, $this->pass, $pass_is_hashed );
    }


   
/**
     * Check a specific permission for this User.
     * This is the MAIN permission check function that you should call to check any permission.
     * This function will delegate to other functions when appropriate.
     *
     * @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, Comment...)
     * @return boolean 0 if permission denied
     */
   
function check_perm( $permname, $permlevel = 'any', $assert = false, $perm_target = NULL )
    {
        global
$Debuglog, $Settings;

        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;
        }

       
$cache_permname = $permname; // save original name because $permname can be changed below
       
$cache_target_ID = isset( $perm_target_ID ) ? $perm_target_ID : 'null';
        if( isset(
$this->cache_perms[$cache_permname][$permlevel][$cache_target_ID] ) )
        {    
// Permission is available in Cache:
           
$Debuglog->add( "Got perm [$cache_permname][$permlevel][$cache_target_ID] from cache", 'perms' );
           
$perm = $this->cache_perms[$cache_permname][$permlevel][$cache_target_ID];
            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.')' : ( is_array( $perm_target ) ? implode( ', ', $perm_target ) : $perm_target ) ).")" );
            }
            return
$perm;
        }


       
$pluggable_perms = array( 'admin', 'spamblacklist', 'slugs', 'templates', 'options', 'files', 'users', 'orgs', 'polls' );
        if(
in_array( $permname, $pluggable_perms ) )
        {
           
$permname = 'perm_'.$permname;
        }
       
//$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!community':
            case
'cats_post!protected':
            case
'cats_post!private':
            case
'cats_post!review':
            case
'cats_post!draft':
            case
'cats_post!deprecated':
            case
'cats_post!redirected':
            case
'cats_item_type_standard':
            case
'cats_item_type_restricted':
            case
'cats_item_type_admin':
               
// 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
'recycle_owncmts':
               
// Check permission to edit comments for own items
               
$Comment = & $perm_target;
               
$Item = & $Comment->get_Item();
               
$blog_ID = $Item->get_blog_ID();
                if(
$Item->creator_user_ID == $this->ID )
                {
// Current user is owner of this item
                   
if( $Item->is_locked() && !$this->check_perm( 'blog_cats', 'edit', false, $blog_ID ) )
                    {
// Comment item is locked and current user is not allowed to edit locked items comment
                       
break;
                    }

                   
$comment_author_User = & $Comment->get_author_User();
                    if( ( empty(
$comment_author_User ) || ( $comment_author_User->level <= $this->level ) )
                        &&
in_array( $Comment->status, array( 'published', 'community', 'protected' ) ) )
                    {
// Comment author is anonymous or his level is lower than current User level, and the Comment was published with some of the above statuses
                        // Check blog user perms to see if user may recycle his own posts comments
                       
$perm = $this->check_perm_blogusers( 'blog_recycle_owncmts', $permlevel, $blog_ID );
                        if( !
$perm )
                        {    
// Check primary and secondary groups for permissions to this specific collection:
                           
$perm = $this->check_perm_bloggroups( 'blog_recycle_owncmts', $permlevel, $blog_ID );
                        }
                    }
                }
                break;

           
// Permissions on a collection:
            // NOTE: these are currently the only collections that will check multiple user groups:
           
case 'blog_ismember':
            case
'blog_workflow_status':
            case
'blog_workflow_user':
            case
'blog_workflow_priority':
            case
'blog_item_propose':
            case
'blog_post_statuses':
            case
'blog_post!published':
            case
'blog_post!community':
            case
'blog_post!protected':
            case
'blog_post!private':
            case
'blog_post!review':
            case
'blog_post!draft':
            case
'blog_post!deprecated':
            case
'blog_post!redirected':
            case
'blog_del_post':
            case
'blog_edit':
            case
'blog_edit_cmt':
            case
'blog_comments':
            case
'blog_comment_statuses':
            case
'blog_del_cmts':
            case
'blog_vote_spam_comments':
            case
'blog_comment!published':
            case
'blog_comment!community':
            case
'blog_comment!protected':
            case
'blog_comment!private':
            case
'blog_comment!deprecated':
            case
'blog_comment!review':
            case
'blog_comment!draft':
            case
'blog_properties':
            case
'blog_cats':
            case
'blog_item_type_standard':
            case
'blog_item_type_restricted':
            case
'blog_item_type_admin':
            case
'blog_edit_ts':
            case
'blog_media_browse':
            case
'blog_analytics':
               
// The owner of a collection has automatic permission to so many things:
               
if( $this->check_perm_blogowner( $perm_target_ID ) )
                {    
// Owner can do *almost* anything:
                   
$perm = true;
                    break;
                }
               
/* continue */
           
case 'blog_admin': // This is what the collection owner does not have automatic access to!

                // Group may grant VIEW access, FULL access:
               
$group_permlevel = ( $permlevel == 'view' ||  $permlevel == 'any' ) ? $permlevel : 'editall';

               
$primary_Group = & $this->get_Group();
                if(
$primary_Group->check_perm( 'blogs', $group_permlevel ) )
                {    
// Primary usergroup grants a global permission:
                   
$perm = true;
                   
// Stop checking other perms:
                   
break;
                }

            case
'blog_can_be_assignee':
               
// Apply next rules below for above collection permissions if current User is not admin or not owner of the Collection:
                // NOTE: The permission "Workflow Member (Items can be assigned to this User/Group)" may be disabled even for admin!

               
if( $perm_target_ID > 0 )
                {    
// Check the permissions below only for requested target collection:

                   
if( $this->check_perm_bloggroups( $permname, $permlevel, $perm_target_ID ) )
                    {    
// Primary or Secondary advanced usergroups permissions on the target collection grant the requested permission:
                       
$perm = true;
                       
// Stop checking other perms:
                       
break;
                    }

                   
// Check user specific perms:
                   
if( $this->check_perm_blogusers( $permname, $permlevel, $perm_target_ID ) )
                    {    
// Advanced user permissions on the target collection grant the requested permission:
                       
$perm = true;
                       
// Stop checking other perms:
                       
break;
                    }

                    if(
$permname == 'blog_properties' && $permlevel == 'copy' )
                    {    
// Check if user can copy the selected collection:
                       
$BlogCache = & get_BlogCache();
                        if(
$perm_Blog = & $BlogCache->get_by_ID( $perm_target_ID, false, false ) )
                        {
                            if(
$this->check_perm( 'blogs', 'create', $assert, $perm_Blog->get( 'sec_ID' ) ) )
                            {    
// If user can create new collection in section of the duplicating collection:
                               
if( $perm_Blog->get_setting( 'allow_duplicate' ) ||
                                   
$this->check_perm( 'blog_properties', 'edit', false, $perm_Blog->ID ) )
                                {    
// If the collection is allowed to duplicate by anyone OR user can edit the duplicating collection:
                                   
$perm = true;
                                }
                            }
                        }
                    }
                }

                break;

            case
'comment!CURSTATUS':
               
/**
                 * @var Comment
                 */
               
$Comment = & $perm_target;
               
// Change the permname to one of the following:
               
$permname = 'comment!'.$Comment->status;
            case
'comment!published':
            case
'comment!community':
            case
'comment!protected':
            case
'comment!private':
            case
'comment!review':
            case
'comment!draft':
            case
'comment!deprecated':
            case
'comment!trash':
               
/**
                 * @var Comment
                 */
               
$Comment = & $perm_target;
               
$Item = & $Comment->get_Item();
               
$blog_ID = $Item->get_blog_ID();
               
$check_status = substr( $permname, 8 );

                if(
$Comment->is_meta() && in_array( $permlevel, array( 'edit', 'moderate', 'delete' ) ) )
                {
// Check the permissions for internal comment with special function
                   
$perm = $this->check_perm( 'meta_comment', $permlevel, false, $Comment );
                    break;
                }

                if( ( (
$permlevel != 'view' ) &&  $Item->is_locked() && !$this->check_perm( 'blog_cats', 'edit', false, $blog_ID ) )
                        && ! (
$permlevel == 'delete' && $this->check_perm( 'users', 'edit' ) ) )
                {
// Comment item is locked, i.e., all of its categories are locked, and current user is not allowed to edit/moderate locked items comment or is not a user admin
                   
break;
                }

                if(
$this->check_perm_blog_global( $blog_ID, $permlevel ) )
                {
// User has global permission on this blog:
                   
$perm = true;
                    break;
                }

                if(
$Comment->status == 'trash' )
                {
// only global group 'editall' perm can give rights to 'trash' status, but this is not the case
                   
break;
                }

                if(
$permlevel == 'delete' )
                {
// permlevel is delete so we have to check the 'blog_del_cmts' permission
                   
$perm = $this->check_perm( 'blog_del_cmts', 'edit', false, $blog_ID )
                            ||
$this->check_perm( 'recycle_owncmts', $permlevel, false, $Comment )
                            ||
$this->check_perm( 'users', 'edit', false );
                    break;
                }

               
// Check comment current status permissions at the blog level:
               
$blog_permname = 'blog_comment!'.$Comment->status;
               
$perm = $perm || $this->check_perm_blogusers( $blog_permname, $permlevel, $blog_ID, $Comment );
                if( !
$perm )
                {    
// Check primary and secondary groups for permissions to this specific collection:
                   
$perm = $this->check_perm_bloggroups( $blog_permname, $permlevel, $blog_ID, $Comment, $this );
                }
                if(
$perm && ( $Comment->status != $check_status ) )
                {
// also check the requested status permissions at the blog level
                   
$blog_permname = 'blog_comment!'.$check_status;
                   
$perm = $this->check_perm_blogusers( $blog_permname, 'create', $blog_ID );
                    if( !
$perm )
                    {    
// Check primary and secondary groups for permissions to this specific collection:
                       
$perm = $this->check_perm_bloggroups( $blog_permname, 'create', $blog_ID );
                    }
                }
                break;

            case
'meta_comment':
               
// Check permission for internal comment:

               
if( $permlevel == 'edit' || $permlevel == 'delete' )
                {    
// Set Comment from target object:
                   
$Comment = & $perm_target;
                    if( empty(
$Comment ) || ! $Comment->is_meta() )
                    {    
// Comment must be defined and meta to check these permissions:
                       
$perm = false;
                        break;
                    }
                   
$Item = & $Comment->get_Item();
                   
$blog_ID = $Item->get_blog_ID();
                }
                elseif(
$permlevel == 'view' || $permlevel == 'add' || $permlevel == 'blog' )
                {    
// Set blog ID from target value:
                   
$blog_ID = $perm_target_ID;
                }
                else
                {    
// Permission level is not allowed for all internal comments (For example 'moderate')
                   
$perm = false;
                    break;
                }

                if( empty(
$blog_ID ) )
                {    
// Item or Blog must be defined to check these permissions:
                   
$perm = false;
                    break;
                }

                if(
$this->check_perm_blog_global( $blog_ID, $permlevel ) )
                {    
// User has global permission on this collection:
                   
$perm = true;
                    break;
                }

                switch(
$permlevel )
                {
                    case
'view':
                    case
'blog': // deprecated perm level
                        // Check perms to View internal comments of the collection:
                       
$perm = // If User is explicitly allowed in the user permissions
                               
$this->check_perm_blogusers( 'meta_comment', 'view', $blog_ID )
                               
// OR If User belongs to primary or secondary groups explicitly allowed in the group permissions
                               
|| $this->check_perm_bloggroups( 'meta_comment', 'view', $blog_ID );
                        break;

                    case
'add':
                       
// Check perms to Add internal comments to the collection items:
                       
$perm = // If User can view internal comment of the collection
                               
$this->check_perm( 'meta_comment', 'view', false, $blog_ID );
                        break;

                    case
'edit':
                       
// Check perms to Edit internal comment:
                       
$perm = // If User is explicitly allowed in the user permissions
                               
$this->check_perm_blogusers( 'meta_comment', 'edit', $blog_ID, $Comment )
                               
// OR If User belongs to primary or secondary groups explicitly allowed in the group permissions
                               
|| $this->check_perm_bloggroups( 'meta_comment', 'edit', $blog_ID, $Comment, $this );
                        break;

                    case
'delete':
                       
// Check perms to Delete internal comments:
                       
$perm = // If it is allowed to delete comments on the collection
                               
$this->check_perm( 'blog_del_cmts', 'edit', false, $blog_ID )
                               
// AND if user can view internal comment of the collection
                               
&& $this->check_perm( 'meta_comment', 'view', false, $blog_ID );
                        break;
                }

                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!community':
            case
'item_post!protected':
            case
'item_post!private':
            case
'item_post!review':
            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();
               
$check_status = substr( $permname, 10 );

                if( ( (
$permlevel != 'view' ) && $Item->is_locked() && !$this->check_perm( 'blog_cats', 'edit', false, $blog_ID ) )
                        && ! (
$permlevel == 'delete' && $this->check_perm( 'users', 'edit', false ) ) )
                {
// Item is locked, i.e., it has all of its categories locked, and current user is not allowed to edit locked items
                    // ( only view permission is allowed by default for locked items ) or is not a user admin
                   
break;
                }

                if(
$this->check_perm_blog_global( $blog_ID, $permlevel ) )
                {
// User has global permission on this blog:
                   
$perm = true;
                    break;
                }

                if(
$permlevel == 'delete' )
                {
// permlevel is delete so we have to check the 'blog_del_post' permission
                    // User admins are allowed to delete posts
                   
$perm = $this->check_perm( 'blog_del_post', 'edit', false, $blog_ID ) || $this->check_perm( 'users', 'edit', false );
                    break;
                }

               
// Check permissions at the blog level:
               
$blog_permname = 'blog_post!'.$Item->status;
               
$perm = $this->check_perm_blogusers( $blog_permname, $permlevel, $blog_ID, $Item );
                if( !
$perm )
                {    
// Check primary and secondary groups for permissions to this specific collection:
                   
$perm = $this->check_perm_bloggroups( $blog_permname, $permlevel, $blog_ID, $Item, $this );
                }
                if(
$perm && ( $Item->status != $check_status ) )
                {
// also check the requested status permissions at the blog level
                   
$blog_permname = 'blog_post!'.$check_status;
                   
$perm = $this->check_perm_blogusers( $blog_permname, 'create', $blog_ID );
                    if( !
$perm )
                    {    
// Check primary and secondary groups for permissions to this specific collection:
                       
$perm = $this->check_perm_bloggroups( $blog_permname, 'create', $blog_ID );
                    }
                }
                break;

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

               
// Group may grant VIEW access, FULL access:
               
if( $this->Group->check_perm( $permname, $permlevel, $perm_target ) )
                {
// 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 primary and secondary groups for permissions to this specific collection:
                       
$perm = $this->check_perm_bloggroups( $permname, $permlevel, $perm_target );
                    }
                }
                elseif(
$permlevel == 'list' && $perm_target == NULL )
                {    
// Check if at least one collection has a perm to view analytics for this user:
                   
$perm = check_coll_first_perm( 'perm_analytics', 'user', $this->ID );
                }
                break;

            case
'user':
               
// Check if user can 'view'/'moderate'/'edit'/'delete' another user
               
$User = & $perm_target;
                if( !
$this->check_status( 'can_view_user', $User->ID ) )
                {
// User can't even view the edited User
                   
break;
                }

               
// If user has global permission on all users then everything is allowed
               
if( $this->check_perm( 'users', 'edit' ) )
                {
// Check if user can edit/delete other users
                   
$perm = true;
                    break;
                }

                if(
$permlevel == 'delete' || $permlevel == 'edit' )
                {
// These permission levels are not allowed for this user
                   
break;
                }

                if( ( !
$Settings->get('allow_anonymous_user_profiles') ) && ( ! $this->check_perm( 'cross_country_allow_profiles' ) ) && ( empty( $this->ctry_ID ) || ( $this->ctry_ID !== $User->ctry_ID ) ) )
                {
// Users can view/browse other users only from the same country, but this user country is empty or not the same as the target user country
                    // this User has no permission to even view the target user
                   
$User->get_Group();
                    if(
$User->Group->level < $Settings->get('allow_anonymous_user_level_min') ||
                       
$User->Group->level > $Settings->get('allow_anonymous_user_level_max') )
                    {
// The anonymous users have no access to view this user
                       
break;
                    }
                }

               
// Note: With this implementation only admin users are allowed to view users from different countries when cross country browsing is restricted
                // check if user has permission to moderate another user
               
if( ( $permlevel == 'moderate' ) && $this->check_perm( 'users', 'moderate' ) )
                {
// this user has moderator permission, check if the group level is higher then the target user group level
                   
$this->get_Group();
                   
$User->get_Group();
                   
$perm = ( $this->Group->level > $User->Group->level );
                    break;
                }

               
// Only 'view' permlevel remained, and this user is allowed to view the target user
               
$perm = true;
                break;

           
// asimo> edit_timestamp permission was converted to blog_edit_ts permission


            // asimo> files permission was converted to pluggable permission
            /*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 primary and secondary groups for permissions for this specific collection:
                        $perm = $this->check_perm_bloggroups( $permname, $permlevel, $perm_target );
                    }
                }
*/
                //break;

           
default:
               
// Check pluggable permissions using user permission check function
               
if( $permname == 'perm_users' && ! $this->check_perm( 'admin', 'normal' ) )
                {    
// User must has Normal Back-office Access to view/moderate/edit other users:
                   
$perm = false;
                    break;
                }
               
$this->get_Group();
               
$perm = Module::check_perm( $permname, $permlevel, $perm_target, 'user_func', $this->Group );
                if(
$perm === true || $perm === NULL )
                {
// If user_func or user perm not exists in the corresponding module then $perm value will be NULL and we have to check the group permission.
                    // If user_func exists and returns true, then we have to check group permission to make sure it does not restrict the user perm.

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

        if(
is_object($perm_target) )
        {    
// Prevent catchable E_FATAL with PHP 5.2 (because there's no __tostring for e.g. Item)
           
$taget_name = get_class($perm_target).'('.$perm_target_ID.')';
        }
        elseif(
is_array($perm_target) )
        {    
// Convert to ID list
           
$taget_name = '('.implode(',', $perm_target).')';
        }
        else
        {
           
$taget_name = $perm_target;
        }

       
$Debuglog->add( 'User perm '.$permname.':'.$permlevel.':'.$taget_name.' => '.($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.')' : ( is_array( $perm_target ) ? implode( ', ', $perm_target ) : $perm_target ) ).")" );
        }

       
// Cache result:
       
$this->cache_perms[$cache_permname][$permlevel][$cache_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
         */
       
$Collection = $Blog = & $BlogCache->get_by_ID( $blog_ID );

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


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

       
$ItemCache = & get_ItemCache();
       
/**
         * @var Item
         */
       
$Item = & $ItemCache->get_by_ID( $item_ID );

        return (
$Item->creator_user_ID == $this->ID );
    }


   
/**
     * Check if user has global perms on this blog
     *
     * @param integer blog ID
     * @param string permlevel
     * @return boolean true if user is the owner, or user group has some permission on all blogs
     */
   
function check_perm_blog_global( $blog_ID, $permlevel = 'edit' )
    {
        if(
$this->check_perm_blogowner( $blog_ID ) )
        {
// user is the blog owner
           
return true;
        }

       
// Group may grant VIEW access, FULL access:
       
$this->get_Group();
       
$group_permlevel = ( $permlevel == 'view' || $permlevel == 'any' ) ? 'viewall' : 'editall';
        if(
$this->Group->check_perm( 'blogs', $group_permlevel ) )
        {
// If group grants a global permission:
           
return true;
        }

        return
false;
    }


   
/**
     * 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_can_be_assignee
     *                  - blog_workflow_status
     *                  - blog_workflow_user
     *                  - blog_workflow_priority
     *                  - blog_post_statuses
     *                  - blog_del_post
     *                  - blog_edit_ts
     *                  - blog_comments
     *                  - blog_cats
     *                  - blog_properties
     * @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, $perm_target = NULL )
    {
       
// Check if user blog advanced perms are loaded
       
if( ! isset( $this->blog_post_statuses[$perm_target_blog] ) )
        {
// Blog post statuses have not been loaded yet:
           
$this->blog_post_statuses[$perm_target_blog] = array();
            if( !
load_blog_advanced_perms( $this->blog_post_statuses[$perm_target_blog], $perm_target_blog, $this->ID, 'bloguser' ) )
            {
// Could not load blog advanced user perms
               
return false;
            }
        }

       
// Check permission and return the result
       
return check_blog_advanced_perm( $this->blog_post_statuses[$perm_target_blog], $this->ID, $permname, $permlevel, $perm_target );
    }


   
/**
     * Check permission for primary and secondary groups of this user on a specified collection
     *
     * This is not for direct use, please call {@link User::check_perm()} instead
     *
     * @see User::check_perm()
     * @param string Permission name
     * @param string Permission level
     * @param integer Permission target blog ID
     * @param object Item that we want to edit
     * @param object User for who we would like to check this permission
     * @return boolean false if permission denied
     */
   
function check_perm_bloggroups( $permname, $permlevel, $perm_target_blog, $perm_target = NULL, $User = NULL )
    {
       
// Get primary group of this User:
       
$primary_Group = & $this->get_Group();

        if(
$primary_Group->check_perm_bloggroups( $permname, $permlevel, $perm_target_blog, $perm_target, $User ) )
        {    
// Primary advanced usergroup permissions on the target collection grant the requested permission:
           
return true;
           
// Stop checking other perms.
       
}

       
// Get secondary usergroups for this User:
       
$secondary_groups = $this->get_secondary_groups();

        if( empty(
$secondary_groups ) )
        {    
// Stop checking other perms if this User has no secondary groups:
           
return false;
        }

       
// Check which secondary usergroup's advanced permissions must still be loaded: (we may have some in cache already)
       
$notloaded_secondary_group_IDs = array();
        foreach(
$secondary_groups as $secondary_Group )
        {
            if( ! isset(
$secondary_Group->blog_post_statuses[ $perm_target_blog ] ) )
            {    
// We must still load advanced permissions for this secondary usergroup:
               
$notloaded_secondary_group_IDs[] = $secondary_Group->ID;
            }
        }

        if(
count( $notloaded_secondary_group_IDs ) )
        {    
// Load advanced permissions of secondary usergroups in a single query:
           
$coll_advanced_perms = NULL;
           
load_blog_advanced_perms( $coll_advanced_perms, $perm_target_blog, $notloaded_secondary_group_IDs, 'bloggroup' );
        }

       
// Find first secondary usergroup that grants the required permission:
       
foreach( $secondary_groups as $secondary_Group )
        {
            if( isset(
$coll_advanced_perms, $coll_advanced_perms[ $secondary_Group->ID ] ) )
            {    
// Set advanced usergroup permissions from array loaded above:
               
$secondary_Group->blog_post_statuses[ $perm_target_blog ] = $coll_advanced_perms[ $secondary_Group->ID ];
            }
            if(
$secondary_Group->check_perm_bloggroups( $permname, $permlevel, $perm_target_blog, $perm_target, $User ) )
            {    
// Secondary usergroup grants requested permissions on the target collection:
               
return true;
               
// Stop checking other groups and other perms.
           
}
        }

       
// All groups of this User have no requested permission on a specified collection:
       
return false;
    }


   
/**
     * Check if user status permits the requested action
     *
     * @param string requested action
     * @param integger target ID - can be a post ID, user ID
     * @return boolean true if the action is permitted, false otherwise
     */
   
function check_status( $action, $target = NULL )
    {
        global
$Settings, $Collection, $Blog;

        switch(
$action )
        {
            case
'can_view_user':
                if(
$Settings->get( 'allow_anonymous_user_profiles' ) || ( !empty( $target ) && ( $target == $this->ID ) ) )
                {
// even anonymous users can see users profile, or user wants to check his own profile
                   
return true;
                }
                return ( (
$this->status == 'activated' ) || ( $this->status == 'autoactivated' ) || ( $this->status == 'manualactivated' ) );
            case
'can_view_users':
                if(
$Settings->get( 'allow_anonymous_user_list' ) )
                {
// even anonymous users can see users list
                   
return true;
                }
                return ( (
$this->status == 'activated' ) || ( $this->status == 'autoactivated' ) || ( $this->status == 'manualactivated' ) );
            case
'can_view_comments':
            case
'can_view_contacts':
            case
'can_view_messages':
            case
'can_view_threads':
            case
'is_validated':
            case
'can_access_admin':
            case
'can_edit_post':
            case
'can_edit_comment':
            case
'can_edit_contacts':
                return ( (
$this->status == 'activated' ) || ( $this->status == 'autoactivated' ) || ( $this->status == 'manualactivated' ) );
            case
'can_report_user':
                if( ! empty(
$target ) )
                {
                   
$UserCache = & get_UserCache();
                   
$target_User = & $UserCache->get_by_ID( $target );
                    return ( (
$this->status == 'activated' ) || ( $this->status == 'autoactivated' ) || ( $this->status == 'manualactivated' ) )
                            && ( ! empty(
$target_User ) && ( $target_User->get( 'level' ) <= $this->get( 'level' ) ) );
                }
                return
false;
            case
'can_be_validated':
                return
in_array( $this->status, array( 'new', 'emailchanged', 'deactivated', 'failedactivation', 'pendingdelete' ) );
            case
'can_view_msgform':
            case
'can_receive_any_message': // can this user receive emails or private messages
           
case 'can_receive_pm':
            case
'can_display_avatar': // can display user's avatar for not admin users
           
case 'can_display_link': // can display user's profile link for not admin users
               
return ( $this->status != 'closed' );
            case
'is_closed':
                return (
$this->status == 'closed' );
            default:
               
debug_die( 'This action is not handled during status check!' );
        }
    }

   
/**
     * Check if the user has the given role in any blog
     *
     * @param string role name, available values ( post_owner, moderator )
     * @return mixed NULL if the given roll name is not defined or there are no blogs, true if the user is super admin, 0 if the user doesn't have the given role, positive number otherwise
     */
   
function check_role( $rolename )
    {
        global
$DB;

        if(
$this->check_perm( 'blogs', 'editall' ) )
        {
// if user has global editall blogs permission then it has any kind of role in all blogs
           
return true;
        }

        switch(
$rolename )
        {
            case
'post_owner':
               
// User is considerated as a post owner, if already has at least one post, or he has right to create posts
               
if( $this->get_num_posts() > 0 )
                {
// User already has at least one post
                   
return true;
                }
               
$role_conditions = array( 'perm_poststatuses' => array( 'IS NOT NULL', '<> ""' ) );
                break;

            case
'member':
               
// User has member role if is member of at least one blog
               
$role_conditions = array( 'ismember' => array( 'IS NOT NULL', '<> 0' ) );
                break;

            case
'comment_editor':
               
// User has permission to edit some other users comments at least in one status
               
$role_conditions = array(
                   
'perm_edit_cmt' => array( 'IS NOT NULL', '<> "no"', '<> "own"' ),
                   
'perm_cmtstatuses' => array( 'IS NOT NULL', '<> 0' )
                );
                break;

            case
'comment_moderator':
               
// set comment moderator perm names
               
$edit_perm_name = 'perm_edit_cmt';
               
$statuses_perm_name = 'perm_cmtstatuses';
            case
'post_moderator':
                if(
$rolename == 'post_moderator' )
                {
// set post moderator perm names
                   
$edit_perm_name = 'perm_edit';
                   
$statuses_perm_name = 'perm_poststatuses';
                }
               
// User is a moderator if has moderator permission at least in one blog
                // A moderator must have permissions to create post/comment with at least two statuses from moderation statuses + published status
               
$check_statuses = get_visibility_statuses( 'moderation' );
               
// Create addition of statuses perm values
               
$perms_value = get_status_permvalue( 'published' );
                foreach(
$check_statuses as $status )
                {
                   
$perms_value = $perms_value + get_status_permvalue( $status );
                }

               
// Check if user has permission to edit other comments than his own and has create permission on at least two statuses defined above
               
$role_conditions = array(
                   
$edit_perm_name => array( 'IS NOT NULL', '<> "no"', '<> "own"' ),
                   
$statuses_perm_name => array( 'IS NOT NULL', 'BIT_COUNT( $perm_field$ & '.$perms_value.' ) > 1' )
                );
                break;

            default:
// roll with the given roll name is not defined
               
return NULL;
        }

       
$where_clause = '';
       
$perm_prefixes = array( 'bloguser_', 'bloggroup_' );
        foreach(
$perm_prefixes as $prefix )
        {
// Check requred perms on blogusers and bloggroups as well
           
$where_part = '';
            foreach(
$role_conditions as $perm_name => $conditions )
            {
// Go through each required permission
               
$perm_field = $prefix.$perm_name;
                foreach(
$conditions as $condition )
                {
// Check all defined conditions and join with 'AND' operator
                   
if( strpos( $condition, '$perm_field$' ) !== false )
                    {
// The $perm_filed must be replaced in the middle of the condition
                       
$where_part .= '( '.str_replace( '$perm_field$', $perm_field, $condition ).' ) AND ';
                    }
                    else
                    {
// The $perm_filed must be added into the beginning of the condition
                       
$where_part .= '( '.$perm_field.' '.$condition.' ) AND ';
                    }
                }
            }
           
// Remove the last ' AND ' from the end of this where clause part
           
$where_part = substr( $where_part, 0, strlen( $where_part ) - 5 );
           
// Add the created conditions to the final where clause
           
$where_clause .= '( '.$where_part.' )';
            if(
$prefix != 'bloggroup_' )
            {
// 'bloggroup_' perm check is the last, but everywhere else we need an 'OR' operator
               
$where_clause .= ' OR ';
            }
        }

       
// Count blog ids where this user has the required permissions for the given role
       
$SQL = new SQL( 'Check user role in all collections' );
       
$SQL->SELECT( 'COUNT( DISTINCT blog_ID )' );
       
$SQL->FROM( 'T_blogs' );
       
$SQL->FROM_add( 'LEFT JOIN T_coll_user_perms ON (blog_advanced_perms <> 0 AND blog_ID = bloguser_blog_ID AND bloguser_user_ID = '.$this->ID.' )' );
       
$SQL->FROM_add( 'LEFT JOIN T_coll_group_perms ON ( blog_advanced_perms <> 0
            AND blog_ID = bloggroup_blog_ID
            AND ( bloggroup_group_ID = '
.$this->grp_ID.'
                  OR bloggroup_group_ID IN ( SELECT sug_grp_ID FROM T_users__secondary_user_groups WHERE sug_user_ID = '
.$this->ID.' ) )
            )'
);
       
$SQL->WHERE( 'blog_owner_user_ID = '.$this->ID );
       
$SQL->WHERE_or( $where_clause );

        return
$DB->get_var( $SQL );
    }


   
/**
     * Check if this user and his group accept receiving private messages or not
     *
     * @return boolean
     */
   
function accepts_pm()
    {
        global
$UserSettings;
        return (
$UserSettings->get( 'enable_PM', $this->ID ) && $this->check_perm( 'perm_messaging', 'reply' ) );
    }


   
/**
     * Check if this user accepts receiving emails and has an email address
     *
     * @return boolean
     */
   
function accepts_email()
    {
        global
$UserSettings, $Settings;
        return (
$Settings->get( 'emails_msgform') != 'never' )
            && (
$UserSettings->get( 'enable_email', $this->ID ) )
            && ( ! empty(
$this->email ) );
    }


   
/**
     * Check if user is owner of at least one collection
     *
     * @return boolean
     */
   
function is_collection_owner()
    {
        if(
is_null( $this->is_collection_owner ) )
        {    
// Get a result from DB first time and put in cache var:
           
global $DB;

           
$check_owner_SQL = new SQL( 'Check if user #'.$this->ID.' is owner of at least one collection' );
           
$check_owner_SQL->SELECT( 'blog_ID' );
           
$check_owner_SQL->FROM( 'T_blogs' );
           
$check_owner_SQL->WHERE( 'blog_owner_user_ID = '.$DB->quote( $this->ID ) );
           
$check_owner_SQL->LIMIT( '1' );

           
$this->is_collection_owner = ($DB->get_var( $check_owner_SQL ) ? true : false);
        }

        return
$this->is_collection_owner;
    }


   
/**
     * Get messaging possibilities between current user and $this user
     *
     * @param object Current User (the one trying to send the PM)
     * @param string Type of contact method to check first: 'PM' > 'email'
     * @return NULL|string allowed messaging possibility: PM > email > login > NULL
     */
   
function get_msgform_possibility( $current_User = NULL, $check_level = 'PM' )
    {
        if( !
$this->is_collection_owner() && ! $this->check_status( 'can_receive_any_message' ) )
        {
// In case of a closed account:
           
return NULL;
        }

        if(
is_logged_in() )
        {
// current User is a registered user/logged in:

           
if( $current_User == NULL )
            {
                global
$current_User;
            }

           
// Check cross-country restrictions:
           
if( ! $this->is_collection_owner() && has_cross_country_restriction( 'contact' ) && ( empty( $current_User->ctry_ID ) || ( $current_User->ctry_ID !== $this->ctry_ID ) ) )
            {
// Contact to this user is not enabled
               
return NULL;
            }

            if(
$check_level == 'PM' && $this->accepts_pm() && $current_User->accepts_pm() && ( $this->ID != $current_User->ID ) )
            {    
// Both users have permission to send or receive Private Messages and not the same user...
                // Check if current_User is allowed to create a new conversation with this user:
               
$blocked_contact = check_blocked_contacts( array( $this->ID ) );
                if( empty(
$blocked_contact ) && ! $current_User->must_accept_terms() )
                {
// User is allowed to send PM to this user, and he didn't reach his new thread limit yet:
                   
return 'PM';
                }
            }

            if(
$this->is_collection_owner() || $this->accepts_email() )
            {
// This User allows email => send email OR Force to allow to contact with this user because he is owner of the selected collection:
               
return 'email';
            }

        }
        else
        {
// current User is anonymous/not logged in:

           
if( $this->is_collection_owner() || $this->accepts_email() )
            {
// This User allows email => send email OR Force to allow to contact with this user because he is owner of the selected collection:
               
return 'email';
            }

            if(
$this->accepts_pm() )
            {
// no email option - try to log in and send private message (only registered users can send PM)
               
return 'login';
            }
        }

       
// No messaging option between current_User and this user:
       
return NULL;
    }


   
/**
     * Get the reason why current User is not able to make a contact with this User
     *
     * This is used when get_msgform_possibility returns NULL;
     *
     * @return string
     */
   
function get_no_msgform_reason()
    {
        global
$current_User;

        if( (
is_logged_in() ) && $this->accepts_pm() )
        {
            if(
has_cross_country_restriction( 'contact' ) && ( empty( $current_User->ctry_ID ) || ( $current_User->ctry_ID !== $this->ctry_ID ) ) )
            {
// Contat to this user is not enabled
               
return T_( 'You are not allowed to contact with this user.' );
            }
            if(
$current_User->accepts_pm() )
            {
// current User accepts private messages
               
if( $current_User->ID == $this->ID )
                {
// current User and recipient user are the same
                   
return T_( 'You cannot send a private message to yourself.' );
                }
               
// current User is not able to contact with this User because User has blocked current User or current User is not in this users contact list
               
return T_( 'This user can only be contacted through private messages, but you are not allowed to send private message to this user.' );
            }
           
// current User has no messaging permission or has disabled private messages in user preferences
           
return T_( 'This user can only be contacted through private messages but you are not allowed to send any private messages.' );
        }
       
// current User is not logged in or this User doesn't want to be contacted
       
return T_( 'This user does not wish to be contacted directly.' );
    }


   
/**
     * Insert object into DB based on previously recorded changes
     *
     * Triggers the plugin event AfterUserInsert.
     *
     * @param boolean TRUE to automatically create new blog if group has permission
     * @return boolean true on success
     */
   
function dbinsert( $create_auto_blog = true )
    {
        global
$Plugins, $DB, $Settings;

       
$DB->begin();

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

            // Save secondary groups:
           
$this->update_secondary_groups();

           
// 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' );
               
// Reset new fields in object:
               
$this->new_fields = array();
            }

           
// NEWSLETTERS:
           
$insert_newsletters = array();
            if( isset(
$Settings ) && ( ! isset( $this->insert_default_newsletters ) || $this->insert_default_newsletters ) )
            {    
// Insert default newsletter subscriptions for this user,
                // Only when general settings are defined (to avoid error on install process),
                // And if it is not disabled for exmaple by widget "Email capture / Quick registration",
               
$insert_newsletters = ( $Settings->get( 'def_newsletters' ) == '' ? array() : explode( ',', trim( $Settings->get( 'def_newsletters' ), ',' ) ) );
            }
            if( ! empty(
$this->new_newsletter_subscriptions ) )
            {    
// Also insert additional newsletters(e-g from widget "Email capture / Quick registration"):
               
$insert_newsletters = array_merge( $insert_newsletters, $this->new_newsletter_subscriptions );
            }
            if( ! empty(
$insert_newsletters ) )
            {    
// Do subscribing if at least one newsletter is selected by default:
               
$this->subscribe( array_unique( $insert_newsletters ) );
            }

           
// USER TAGS:
           
if( $result )
            {
// Let's handle the tags
               
$this->insert_update_user_tags( 'insert' );
            }

           
// 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(
$create_auto_blog && $Group->check_perm( 'perm_getblog', 'allowed' ) )
            {
// automatically create new blog for this user
                // TODO: sam2kb> Create a blog only when this user is validated!
               
$new_Blog = new Blog( NULL );
               
$shortname = $this->get( 'login' );
               
$new_Blog->set( 'sec_ID', $Group->get_setting( 'perm_default_sec_ID' ) );
               
$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' ) ) );

               
// Defines blog settings by its kind.
               
$Plugins->trigger_event( 'InitCollectionKinds', array(
                               
'Blog' => & $new_Blog,
                               
'kind' => 'std',
                            ) );

               
$new_Blog->create();

               
// Don't show a sample collection on top menu in back-office:
                // TODO: In another branch Erwin has implemented a rule similar to "only enable first 10 collections". This will be merged here at some point.
               
$new_Blog->favorite( NULL, 0 );
            }

           
// Save IP Range and user counter
           
antispam_increase_counter( 'user' );
        }

       
$DB->commit();

        return
$result;
    }


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

       
$DB->begin();

       
$result = parent::dbupdate();

       
// Save secondary groups:
       
$this->update_secondary_groups();

       
// Update existing fields:
       
if( !empty($this->updated_fields) )
        {
            foreach(
$this->updated_fields as $uf_ID => $uf_val )
            {
               
// Note the updated_fields key values must be integers, so don't need casting or DB->quote()
               
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 );
                }
            }
           
// Reset updated fields in object:
           
$this->updated_fields = array();
        }

       
// 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' );
           
// Reset new fields in object:
           
$this->new_fields = array();
        }

       
// Update newsletter subscriptions:
       
if( $this->newsletter_subscriptions_updated )
        {    
// Only if they have been changed:
            // Insert newsletter subscriptions for this user:
           
$this->subscribe( $this->new_newsletter_subscriptions );
           
// Unsubscribe from unchecked newsletters:
           
$this->unsubscribe( array_diff( $this->get_newsletter_subscriptions( 'all' ), $this->new_newsletter_subscriptions ) );
        }

       
// Update user tags:
       
if( isset( $this->dbchanges_flags['user_tags'] ) )
        {
// Let's handle the tags:
           
$this->insert_update_user_tags( 'update' );
        }

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

        if( isset(
$this->previous_status ) &&
            !
in_array( $this->previous_status, array( 'activated', 'manualactivated', 'autoactivated' ) ) &&
           
in_array( $this->get( 'status' ), array( 'activated', 'manualactivated', 'autoactivated' ) ) )
        {    
// Complete activation if user has been activated:
           
$this->complete_activation();
        }

       
$DB->commit();

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

        return
true;
    }


   
/**
     * Delete those users from the database which corresponds to the given condition or to the given ids array
     * Note: the delete cascade arrays are handled!
     *
     * @param string where condition
     * @param array object ids
     * @param array additional params if required
     * @return mixed # of rows affected or false if error
     */
   
static function db_delete_where( $sql_where, $object_ids = NULL, $params = NULL )
    {
        global
$DB;

       
$DB->begin();

        if( ! empty(
$sql_where ) )
        {
           
$object_ids = $DB->get_col( 'SELECT user_ID FROM T_users WHERE '.$sql_where );
        }

        if( !
$object_ids )
        {
// There is no user to delete
           
$DB->commit();
            return;
        }

       
// Delete orphan threads per users
       
$result = delete_orphan_threads( $object_ids );

       
// Remove deleted user(s) from posts where it was as last edit user
       
$user_ids = implode( ',', $object_ids );
       
$result = $result && ( $DB->query( 'UPDATE T_items__item
                                    SET post_lastedit_user_ID = NULL
                                  WHERE post_lastedit_user_ID IN ( '
.$user_ids.' )' ) !== false );
       
$result = $result && ( $DB->query( 'UPDATE T_items__version
                                    SET iver_edit_user_ID = NULL
                                  WHERE iver_edit_user_ID IN ( '
.$user_ids.' )' ) !== false );

       
// Remove this user from links where it was as last edit user
       
$result = $result && ( $DB->query( 'UPDATE T_links
                                    SET link_lastedit_user_ID = NULL
                                  WHERE link_lastedit_user_ID IN ( '
.$user_ids.' )' ) !== false );

        if(
$result )
        {
// Delete the user(s) with all of the cascade objects
           
$params['use_transaction'] = false; // no need to start a new transaction
           
$result = parent::db_delete_where( $sql_where, $object_ids, $params );
        }

        if(
$result !== false )
        {
// delete the users' media folders recursively, for all deleted users
           
$FileRootCache = & get_FileRootCache();
            foreach(
$object_ids as $user_ID )
            {
                if(
$root_directory = $FileRootCache->get_root_dir( 'user', $user_ID ) )
                {
// Delete the folder only when it is detected:
                   
rmdir_r( $root_directory );
                }
            }
        }

        (
$result !== false ) ? $DB->commit() : $DB->rollback();

        return
$result;
    }


   
/**
     * 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 = array() )
    {
        global
$DB, $Plugins, $current_User;

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

        if(
$this->ID == 1 ||
            (
is_logged_in() && $this->ID == $current_User->ID ) )
        {    
// Don't allow to delete first admin user and current logged in user:
           
return false;
        }

       
$deltype = param( 'deltype', 'string', '' ); // spammer

        // Check to delete additional data if we are deleting user as spammer or it is checked on the submitted form:
       
$delete_messages = ( $deltype == 'spammer' || param( 'force_delete_messages', 'integer', 0 ) );
       
$delete_comments = ( $deltype == 'spammer' || param( 'force_delete_comments', 'integer', 0 ) );
       
$delete_files    = ( $deltype == 'spammer' || param( 'force_delete_files', 'integer', 0 ) );

       
$DB->begin();

       
$this->init_relations( array(
               
'delete_messages' => $delete_messages,
               
'delete_comments' => $delete_comments,
               
'delete_files'    => $delete_files,
            ) );

        if( !
$delete_comments )
        {    
// Keep user's comments as from anonymous user,
            // Transform registered user comments to unregistered:
           
$ret = $DB->query( 'UPDATE T_comments
                                                    SET comment_author_user_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_user_ID = '
.$this->ID );
            if(
$Log instanceof log )
            {
               
$Log->add( 'Transforming user\'s comments to unregistered comments... '.sprintf( '(%d rows)', $ret ), 'note' );
            }
        }

        if( !
$delete_files )
        {    
// Keep user's files as from anonymous user:
           
$ret = $DB->query( 'UPDATE T_files
                  SET file_creator_user_ID = NULL
                WHERE file_creator_user_ID = '
.$this->ID. ' AND file_root_type != "user"' );
           
$ret = $DB->query( 'UPDATE T_links
                  SET link_creator_user_ID = NULL
                WHERE link_creator_user_ID = '
.$this->ID );
            if(
$Log instanceof log )
            {
               
$Log->add( 'Setting user\'s uploaded files creator ID to NULL...'.sprintf( '(%d rows)', $ret ), 'note' );
            }
        }

       
// remember ID, because parent method resets it to 0
       
$old_ID = $this->ID;
       
$old_email = $this->get( 'email' );

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

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

        if(
$deltype == 'spammer' )
        {
// User was deleted as spammer, we should mark email of this user as 'Spammer'
           
$EmailAddressCache = & get_EmailAddressCache();
           
$EmailAddress = & $EmailAddressCache->get_by_name( $old_email, false, false );
            if( !
$EmailAddress )
            {    
// Create new record in the T_email_address table
               
$EmailAddress = new EmailAddress();
               
$EmailAddress->set( 'address', $old_email );
            }
            if( !empty(
$EmailAddress ) )
            {
// Save status of an email address
               
$EmailAddress->set( 'status', 'spammer' );
               
$EmailAddress->dbsave();
            }
        }

       
$DB->commit();

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

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

       
// BLOCK CACHE INVALIDATION:
        // This User has been modified, cached content depending on it should be invalidated:
       
BlockCache::invalidate_key( 'user_ID', $old_ID );

        return
true;
    }


   
/**
     * 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).
     * @param integer Collection ID
     * @param boolean TRUE if user email is changed
     * @return boolean True, if the email could get sent; false if not
     */
   
function send_validate_email( $redirect_to_after, $blog = NULL, $email_changed = false )
    {
        global
$app_name, $Session, $baseurl, $servertimenow;
        global
$Settings, $UserSettings;

        if( ! empty(
$this->welcome_activate_email_campaign_sent ) )
        {    
// This flag means we don't need to send a separate activation email
            // because at least one special(Welcome-Activate) email campaign was sent
            // on auto newsletter subscribing before calling this function.
            // Return TRUE in order to display the same info messages what user sees on normal activation,
            // because Welcome-Activate email campaign may contains button/link to activate user account:
           
return true;
        }

       
// Display messages depending on user email status
       
display_user_email_status_message( $this->ID );

        if( empty(
$redirect_to_after ) )
        {    
// If redirect to was not set:
           
$redirect_to_after = param( 'redirect_to', 'url', '' );
            if( empty(
$redirect_to_after ) )
            {
                if(
is_admin_page() )
                {
                   
$redirect_to_after = regenerate_url( 'action' );
                }
                else
                {
                   
$redirect_to_after = $this->get_userpage_url();
                }
            }
        }

        if(
$Settings->get( 'validation_process' ) == 'easy' )
        {    
// Validation process is set to easy, send and easy activation email:
           
return send_easy_validate_emails( array( $this->ID ), false, $email_changed, $redirect_to_after );
        }

       
// Secure validation process:

       
if( mail_is_blocked( $this->email ) )
        {
// prevent trying to send an email to a blocked email address ( Note this is checked in the send_easy_validate_emails too )
           
return false;
        }

       
$request_id = generate_random_key(22);

       
$blog_param = ( empty( $blog ) ) ? '' : '&inskin=1&blog='.$blog;

       
// Change locale here to localize the email subject and content
       
locale_temp_switch( $this->get( 'locale' ) );
       
$email_template_params = array(
               
'status'     => $this->status,
               
'blog_param' => $blog_param,
               
'request_id' => $request_id,
            );
       
$r = send_mail_to_User( $this->ID, sprintf( T_( 'Activate your account: %s' ), '$login$' ), 'account_activate', $email_template_params, true );
       
locale_restore_previous();

        if(
$r )
        {
// save request_id into Session
           
$request_ids = $Session->get( 'core.activateacc.request_ids' );
            if( ( !
is_array($request_ids) ) || $email_changed )
            {
// create new request ids array if it doesn't exist yet, or if user email changed ( this way the old request into the old email address won't be valid )
               
$request_ids = array();
            }
           
$request_ids[] = $request_id;
           
$Session->set( 'core.activateacc.request_ids', $request_ids, 86400 * 2 ); // expires in two days (or when clicked)
            // set a redirect_to session variable because this way after the account will be activated we will know where to redirect
           
$Session->set( 'core.activateacc.redirect_to', $redirect_to_after  );
           
$Session->dbsave(); // save immediately

            // update last activation email timestamp
           
$UserSettings->set( 'last_activation_email', date2mysql( $servertimenow ), $this->ID );
           
$UserSettings->dbupdate();
        }

        return
$r;
    }


   
/**
     * Activate user account after user clicks on activate link from email
     */
   
function activate_from_Request()
    {
       
// Activate current user account:
       
$this->set( 'status', 'activated' );
       
$this->dbupdate();
    }


   
/**
     * Do actions to complete activation for this user account
     */
   
function complete_activation()
    {
        global
$DB, $Settings, $UserSettings, $current_User;

        if( empty(
$UserSettings ) )
        {    
// initialize UserSettings:
           
load_class( 'users/model/_usersettings.class.php', 'UserSettings' );
           
$UserSettings = new UserSettings();
        }

       
// Clear last reminder key and last activation email date because the user was activated:
       
$UserSettings->delete( 'last_activation_reminder_key', $this->ID );
       
$UserSettings->delete( 'last_activation_email', $this->ID );
       
$UserSettings->delete( 'activation_reminder_count', $this->ID );
       
$UserSettings->delete( 'send_activation_reminder', $this->ID );
       
$UserSettings->dbupdate();

        if(
$Settings->get( 'newusers_findcomments' ) )
        {    
// We have to assign the all old comments from current user by email:
           
$DB->query( 'UPDATE T_comments
                   SET comment_author_user_ID = '
.$DB->quote( $this->ID ).'
                 WHERE comment_author_email = '
.$DB->quote( $this->email ).'
                   AND comment_author_user_ID IS NULL'
,
               
'Assign anonymous comments to register user after activation' );
           
// Subscribe user to posts where he selected "Notify me of replies" on creating those anonymous comments:
           
$SQL = new SQL();
           
$SQL->SELECT( 'DISTINCT comment_item_ID, comment_author_user_ID, 1' );
           
$SQL->FROM( 'T_comments' );
           
$SQL->WHERE( 'comment_author_user_ID = '.$DB->quote( $this->ID ) );
           
$SQL->WHERE_and( 'comment_anon_notify = 1' );
           
$DB->query( 'REPLACE INTO T_items__subscriptions ( isub_item_ID, isub_user_ID, isub_comments ) '.$SQL->get(),
               
'Subscribe user to posts where he selected "Notify me of replies" on creating those anonymous comments' );
        }

       
// Create a welcome private message when user's status was changed to Active:
       
$this->send_welcome_message();

       
// Send notification email about activated account to users with edit users permission:
       
send_admin_notification( NT_('New user account activated'), 'account_activated', array(
               
'User' => $this,
               
'login' => $this->login, // this is required in the send_admin_notification
                // If admin activated some other user account:
               
'activated_by_admin' => ( is_logged_in() && $current_User->ID != $this->ID ? $current_User->get_username() : '' ),
            ) );
    }


   
// 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;
        }

       
$available_msgform = $this->get_msgform_possibility();
        if( !
$available_msgform )
        {    
// There is no way this user accepts receiving messages.
           
return false;
        }

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

        if(
param( 'redirect_to', 'url', NULL ) !== NULL )
        {    
// Use current redirect URL:
           
$redirect_to = get_param( 'redirect_to' );
        }
        else
        {    
// Generate new redirect URL:
           
$redirect_to = regenerate_url( '', '', '', '&' );
        }

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

        if(
$title == '#' )
        {
            switch(
$available_msgform )
            {
                case
'email':
                   
$title = T_('Send email to user');
                    break;
                case
'PM':
                case
'login':
                default:
                   
$title = T_('Send message to user');
                    break;
            }
        }
        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 );
    }


   
/**
     * Return gender of the user
     */
   
function get_gender()
    {
        switch(
$this->gender )
        {
            case
'M':
                return
T_('A man');

            case
'F':
                return
T_('A woman');

            case
'O':
                return
T_('Other');
        }

        return
NULL;
    }


   
/**
     * Return attr class depending on gender of the user
     */
   
function get_gender_class()
    {
       
$gender_class = 'user';

        if(
$this->check_status( 'is_closed' ) )
        {
// Set different gender color if user is closed
           
return 'user closed';
        }

        if( !
check_setting( 'gender_colored' ) )
        {
// Don't set gender color if setting is OFF
           
return $gender_class;
        }

        switch(
$this->gender )
        {
// Set a class name for each gender type
           
case 'M':
               
$gender_class .= ' man';
                break;
            case
'F':
               
$gender_class .= ' woman';
                break;
            case
'O':
               
$gender_class .= ' other';
                break;
            default:
               
$gender_class .= ' nogender';
                break;
        }

        return
$gender_class;
    }


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


   
/**
     * Template function: display ICQ of the user
     * @deprecated
     */
   
function icq( $format = 'htmlbody' )
    {
    }


   
/**
     * Template function: display AIM of the user.
     * @deprecated
     */
   
function aim( $format = 'htmlbody' )
    {
    }


   
/**
     * Template function: display Yahoo IM of the user
     * @deprecated
     */
   
function yim( $format = 'htmlbody' )
    {
    }


   
/**
     * Template function: display MSN of the user
     * @deprecated
     */
   
function msn( $format = 'htmlbody' )
    {
    }

   
// }}}


   
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 {@link Link} object of the user's avatar.
     *
     * @return Link This may be NULL.
     */
   
function & get_avatar_Link()
    {
       
$Link = NULL;

        if(
$this->has_avatar() )
        {
           
$LinkOwner = new LinkUser( $this );
           
$Link = $LinkOwner->get_link_by_file_ID( $this->avatar_file_ID );
        }

        return
$Link;
    }


   
/**
     * Get array of {@link Link} objects of the previously uploaded avatars.
     *
     * @param boolean TRUE - to exclude main picture from list
     * @param boolean TRUE - to ignore the settings and get the avatars anyway
     * @return array of Links
     */
   
function get_avatar_Links( $exclude_main_picture = true, $ignore_settings = false )
    {
        global
$Settings;

       
$avatar_Links = array();

        if( !
$ignore_settings && !( $Settings->get('upload_enabled') && $Settings->get( 'fm_enable_roots_user' ) ) )
        {
// Upload is not enabled and we have no permission to use it...
           
return $avatar_Links;
        }

       
$LinkOwner = new LinkUser( $this );
        foreach(
$LinkOwner->get_Links() as $user_Link )
        {
           
$l_File = & $user_Link->get_File();
            if( ! empty(
$l_File ) && $l_File->is_image() )
            {
                if(
$exclude_main_picture && $l_File->ID == $this->avatar_file_ID )
                {
// Exclude the main picture from list of other pictures
                   
continue;
                }
               
$avatar_Links[] = $user_Link;
            }
        }

        return
$avatar_Links;
    }


   
/**
     * Get avatar IMG tag.
     *
     * @param string size
     * @param string class
     * @param string align
     * @param boolean true if the avatar image should be zoomed on click, false otherwise
     * @param string avatar overlay text
     * @param string group name for lightbox plugin
     * @param string Change size of the attributes "width" & "height".
     *               Example: ( $tag_size = '160' ) => width="160" height="160"
     *                        ( $tag_size = '160x320' ) => width="160" height="320"
     *                        NULL - use real size
     * @param string Protocol is used for gravatar, example: 'http:' or 'https:'
     * @return string
     */
   
function get_avatar_imgtag( $size = 'crop-top-64x64', $class = 'avatar', $align = '', $zoomable = false, $avatar_overlay_text = '', $lightbox_group = '', $tag_size = NULL, $protocol = '' )
    {
       
/**
         * @var Link
         */
       
$Link = & $this->get_avatar_Link();

       
/**
         * @var File
         */
       
if( ! $Link || ! $File = & $Link->get_File() )
        {
// User doesn't have an avatar
           
return get_avatar_imgtag_default( $size, $class, $align, array(
                   
'email'    => $this->get( 'email' ),
                   
'gender'   => $this->get( 'gender' ),
                   
'tag_size' => $tag_size,
                   
'protocol' => $protocol,
                ) );
        }

        if( !
$this->check_status( 'can_display_avatar' ) && ! ( is_admin_page() && check_user_perm( 'users', 'edit', false, NULL, false ) ) )
        {
// if the user status doesn't allow to display avatar and current User is not an admin in admin interface, then show default avatar
           
return get_avatar_imgtag_default( $size, $class, $align, array(
                   
'email'    => $this->get( 'email' ),
                   
'gender'   => $this->get( 'gender' ),
                   
'tag_size' => $tag_size,
                   
'protocol' => $protocol,
                ) );
        }

        if(
$zoomable )
        {
// return clickable avatar tag, zoom on click
           
if( is_logged_in() )
            {
// Only logged in users can see a big picture of the avatar
                // set random value to link_rel, this way the pictures on the page won't be grouped
                // this is usefull because the same avatar picture may appear more times in the same page
               
if( empty( $lightbox_group ) )
                {
                   
$link_rel = 'lightbox[l'.$Link->ID.rand(0, 100000).']';
                }
                else
                {
                   
$link_rel = 'lightbox['.$lightbox_group.']';
                }
               
$r = $Link->get_tag( array(
                       
'before_image'        => '',
                       
'before_image_legend' => '',
                       
'after_image_legend'  => '',
                       
'after_image'         => '',
                       
'image_size'          => $size,
                       
'image_link_title'    => $this->get_username(),
                       
'image_link_rel'      => $link_rel,
                       
'image_class'         => $class,
                       
'image_align'         => $align,
                       
'tag_size'            => $tag_size,
                    ) );
            }
            else
            {
// Anonymous user get an avatar picture with link to login page
               
global $Collection, $Blog;
               
$redirect_to = '';
                if( isset(
$Blog ) )
                {
// Redirect user after login
                   
$redirect_to = $Blog->get( 'userurl', array( 'user_ID' => $this->ID, 'user_login' => $this->login ) );
                }
               
$r = '<a href="'.get_login_url( 'cannot see avatar', $redirect_to ) .'">'.$File->get_thumb_imgtag( $size, $class, $align, '', $tag_size ).'</a>';
            }
        }
        else
        {
           
$r = $File->get_thumb_imgtag( $size, $class, $align, '', $tag_size );
        }

        if(
$r != '' && $avatar_overlay_text != '' )
        {
// Add overlay text if it is enabled
           
$r = $this->get_avatar_overlay_text( $r, $size, $avatar_overlay_text, $class, $File );
        }

        return
$r;
    }


   
/**
     * Get overlay text for avatar img tag
     *
     * @param string Tag <img />
     * @param array Avatar size, keys: 'width' & 'height'
     * @param string Avatar overlay text
     * @param string Class
     * @param object File
     * @return string Tag <img /> with overlay text
     */
   
function get_avatar_overlay_text( $img_tag, $thumb_size, $avatar_overlay_text, $class = '', $File = NULL )
    {
       
preg_match( '/ width="(\d+)" height="(\d+)" /i', $img_tag, $img_sizes );
        if(
count( $img_sizes ) == 3 )
        {
// img tag has a defined width & height
           
$width = $img_sizes[1];
           
$height = $img_sizes[2];
        }
        else
        {
// We try to get a sizes from config
           
global $thumbnail_sizes;
            if( isset(
$thumbnail_sizes[ $thumb_size ] ) )
            {
// Set a sizes
               
if( ! is_null( $File ) && $thumb_sizes = $File->get_thumb_size( $thumb_size ) )
                {
// Get sizes that are used for thumbnail really
                   
list( $width, $height ) = $thumb_sizes;
                }
                else
                {
// Use the size of thumbnail config if File is not defined
                   
$width = $thumbnail_sizes[ $thumb_size ][1];
                   
$height = $thumbnail_sizes[ $thumb_size ][2];
                }
            }
        }

        if( empty(
$width ) || empty( $height ) )
        {
// If sizes is not defined we cannot calculate a font-size for an overlay text
           
return $img_tag;
        }

       
$overlay_lines = explode( "\n", str_replace( "\r", '', $avatar_overlay_text ) );
       
$max_line_length = 0;
        foreach(
$overlay_lines as $line )
        {
// Find the most long line of the overlay text
           
if( $max_line_length < strlen($line) )
            {    
// Get max long line
               
$max_line_length = strlen($line);
            }
        }
        if(
$max_line_length > 0 )
        {
// Display an overlay text if max length is defined
            // Calculate approximate font size, 1.7 - is custom coefficient of the font
           
$font_size = ceil( ( $width / $max_line_length ) * 1.7 );
           
// Set line-height for centering text by vertical
           
$line_height = ceil( ( $height / count( $overlay_lines ) ) * 0.82 );
           
// Padding-top give us a vertical centering
           
$padding_top = ceil( $line_height * 0.32 );

           
$tag_is_linked = false;
            if(
strpos( $img_tag, '</a>' ) !== false )
            {
// img_tag is located inside tag <a>, we should to remove a end of the tag and then add
               
$img_tag = str_replace( '</a>', '', $img_tag );
               
$tag_is_linked = true;
            }
           
$img_tag = '<div class="bubletip_overlay_text '.$class.'">'.
                   
$img_tag.
                   
'<div style="font-size:'.$font_size.'px;line-height:'.$line_height.'px;padding-top:'.$padding_top.'px">'.
                       
nl2br($avatar_overlay_text).
                   
'</div>';
            if(
$tag_is_linked )
            {
// Add end of the tag which is removed above
               
$img_tag .= '</a>';
            }
           
$img_tag .= '</div>';
        }

        return
$img_tag;
    }


   
/**
     * Get styled avatar
     *
     * @param array params
     * @return string
     */
   
function get_avatar_styled( $params = array() )
    {
        global
$thumbnail_sizes;

       
$params = array_merge( array(
               
'block_class'  => 'avatar_rounded',
               
'size'         => 'crop-top-64x64',
               
'avatar_class' => 'avatar',
               
'zoomable'     => false,
               
'overlay_text' => '',
               
'show_login'   => true,
               
'bubbletip'    => true,
            ),
$params );

       
$bubbletip_param = '';
        if(
$params['bubbletip'] )
        {    
// Init bubbletip param
           
$bubbletip_param = 'rel="bubbletip_user_'.$this->ID.'"';
        }

       
$style_width = '';
        if( isset(
$thumbnail_sizes[$params['size']] ) )
        {
           
$style_width = ' style="width:'.$thumbnail_sizes[$params['size']][1].'px"';
        }

       
$identity_url = get_user_identity_url( $this->ID );

        if( !empty(
$identity_url ) )
        {
           
$r = '<a href="'.$identity_url.'" class="'.$params['block_class'].'"'.$bubbletip_param.$style_width.'>';
        }
        else
        {
           
$r = '<div class="'.$params['block_class'].'"'.$bubbletip_param.$style_width.'>';
        }

       
$r .= $this->get_avatar_imgtag( $params['size'], $params['avatar_class'], '', $params['zoomable'], $params['overlay_text'] );

        if(
$params['show_login'] )
        {    
// Display user name
           
$r .= $this->get_colored_login( array( 'login_text' => 'name' ) );
        }

       
$r .= !empty( $identity_url ) ? '</a>' : '</div>';

        return
$r;
    }


   
/**
     * Add a user field
     *
     * @param integer User field definition ID
     * @param string Field value
     */
   
function userfield_add( $uf_ufdf_ID, $val )
    {
        global
$DB;
       
$this->new_fields[] = $uf_ufdf_ID.', '.$DB->quote( $val );
    }


   
/**
     * Update an user field. Empty fields will be deleted on dbupdate.
     *
     * @param integer User field ID
     * @param string Field value
     */
   
function userfield_update( $uf_ID, $val )
    {
       
$this->updated_fields[ $uf_ID ] = $val;
    }


   
/**
     * Get all user extra fields with selected type
     *
     * @param string Field type: 'email', 'word', 'number', 'phone', 'text'
     * @param boolean TRUE to get only the fields with icon
     * @return array Fields
     */
   
function userfields_by_type( $field_type, $only_with_icon = true )
    {
        global
$DB;

       
$SQL = new SQL( 'Get values of user fields by type "'.$field_type.'" for User #'.$this->ID );
       
$SQL->SELECT( 'uf_varchar, ufdf_ID, ufdf_icon_name, ufdf_code' );
       
$SQL->FROM( 'T_users__fields' );
       
$SQL->FROM_add( 'INNER JOIN T_users__fielddefs ON uf_ufdf_ID = ufdf_ID' );
       
$SQL->WHERE( 'uf_user_ID = '.$this->ID );
       
$SQL->WHERE_and( 'ufdf_type = '.$DB->quote( $field_type ) );
        if(
$only_with_icon )
        {
           
$SQL->WHERE_and( 'ufdf_icon_name IS NOT NULL' );
        }
       
$SQL->ORDER_BY( 'ufdf_order' );

        return
$DB->get_results( $SQL );
    }


   
/**
     * Get user fields by field definition ID
     *
     * @param integer Field definition ID
     * @return array|false Fields
     */
   
function userfields_by_ID( $ufdf_ID )
    {
       
// Load all user fields once:
       
$this->userfields_load();

        if( ! empty(
$this->userfields_by_type[ $ufdf_ID ] ) )
        {    
// Get user fields from cache:
           
$userfields = array();
            foreach(
$this->userfields_by_type[ $ufdf_ID ] as $uf_ID )
            {
                if( ! empty(
$this->userfields[ $uf_ID ] ) )
                {
                   
$userfields[] = $this->userfields[ $uf_ID ];
                }
            }
            return
$userfields;
        }

       
// No fields:
       
return false;
    }


   
/**
     * Get user field value by ID
     *
     * @param integer Field ID
     * @param boolean TRUE to prepare user field for correct html displaying
     * @return string Field value
     */
   
function userfield_value_by_ID( $field_ID, $prepare_userfield = true )
    {
       
// Load all user fields once:
       
$this->userfields_load();

        if( isset(
$this->userfields_by_type[ $field_ID ], $this->userfields[ $this->userfields_by_type[ $field_ID ][0] ] ) )
        {    
// Get value from cache:
           
$userfield = $this->userfields[ $this->userfields_by_type[ $field_ID ][0] ];

            if(
$prepare_userfield )
            {    
// Prepare user field for correct html displaying:
               
userfield_prepare( $userfield );
            }

            return
$userfield->uf_varchar;
        }

       
// No field value
       
return '';
    }


   
/**
     * Get user field values by code
     *
     * @param integer Field code
     * @param boolean TRUE to prepare user field for correct html displaying
     * @return array Field values
     */
   
function userfield_values_by_code( $field_code, $prepare_userfield = true )
    {
        global
$DB;

       
// Load all user fields once
       
$this->userfields_load();

       
$field_values = array();
        if( ! empty(
$this->userfields_by_code[ $field_code ] ) )
        {
// Get value from cache
           
foreach( $this->userfields_by_code[ $field_code ] as $userfield_ID )
            {
               
$userfield = $this->userfields[ $userfield_ID ];
                if(
$prepare_userfield )
                {    
// Prepare user field for correct html displaying:
                   
userfield_prepare( $userfield );
                }
               
$field_values[] = $userfield->uf_varchar;
            }
        }

        return
$field_values;
    }


   
/**
     * Load userfields
     */
   
function userfields_load()
    {
        if(
$this->userfields_loaded )
        {
// The user fields already were loaded, Don't call DB query twice
           
return;
        }

        global
$DB;

       
$SQL = new SQL( 'Load values of user fields for User #'.$this->ID );
       
$SQL->SELECT( 'uf_ID, uf_varchar, ufgp_ID, ufgp_name, T_users__fielddefs.*' );
       
$SQL->FROM( 'T_users__fields' );
       
$SQL->FROM_add( 'INNER JOIN T_users__fielddefs ON uf_ufdf_ID = ufdf_ID' );
       
$SQL->FROM_add( 'INNER JOIN T_users__fieldgroups ON ufdf_ufgp_ID = ufgp_ID' );
       
$SQL->WHERE( 'uf_user_ID = '.$this->ID );
       
$SQL->WHERE_and( 'ufdf_required != "hidden"' );
       
$SQL->ORDER_BY( 'ufgp_order, ufdf_order, uf_ID' );
       
$userfields = $DB->get_results( $SQL );

       
$userfield_lists = array();
        foreach(
$userfields as $u => $userfield )
        {
            if( !
userfield_is_viewable( $this->ID, $userfield->ufdf_visibility, $userfield->ufdf_type ) )
            {    
// Current user cannot view this user field:
               
unset( $userfields[ $u ] );
                continue;
            }

            if(
$userfield->ufdf_duplicated == 'list' )
            {
// Prepare a values of list into one array
               
if( !isset( $userfield_lists[$userfield->ufdf_ID] ) )
                {    
// Init array
                   
$userfield_lists[$userfield->ufdf_ID] = array();
                }
               
$userfield_lists[$userfield->ufdf_ID][$userfield->uf_ID] = $userfield->uf_varchar;
            }
        }

        foreach(
$userfields as $userfield )
        {
            if(
$userfield->ufdf_duplicated == 'list' )
            {
// List style
               
if( isset( $userfield_lists[$userfield->ufdf_ID] ) )
                {
// Save all data for this field:
                   
$userfield->list = $userfield_lists[ $userfield->ufdf_ID ];
                   
$userfield->uf_varchar = implode( ', ', $userfield_lists[$userfield->ufdf_ID] );
                   
$this->userfields[$userfield->uf_ID] = $userfield;
                   
$this->userfields_by_code[$userfield->ufdf_code][] = $userfield->uf_ID;
                   
// Unset array to avoid a duplicates
                   
unset( $userfield_lists[$userfield->ufdf_ID] );
                }
            }
            else
            {
// Save all data for this field:
               
$this->userfields[$userfield->uf_ID] = $userfield;
               
$this->userfields_by_code[$userfield->ufdf_code][] = $userfield->uf_ID;
            }
           
// Save index
           
$this->userfields_by_type[$userfield->ufdf_ID][] = $userfield->uf_ID;
        }

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

       
// Set flag to don't call this function twice:
       
$this->userfields_loaded = true;
    }


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

        if( ! isset(
$this->userfield_defs ) )
        {
           
$SQL = new SQL( 'Load user field definitions' );
           
$SQL->SELECT( 'ufdf_ID, ufdf_type, ufdf_name, ufdf_required, ufdf_options, ufdf_duplicated, ufdf_visibility' );
           
$SQL->FROM( 'T_users__fielddefs' );
           
$userfield_defs = $DB->get_results( $SQL );

            foreach(
$userfield_defs as $userfield_def )
            {
               
$this->userfield_defs[$userfield_def->ufdf_ID] = array(
                   
$userfield_def->ufdf_type,
                   
$userfield_def->ufdf_name,
                   
$userfield_def->ufdf_required,
                   
$userfield_def->ufdf_options,
                   
$userfield_def->ufdf_duplicated,
                   
$userfield_def->ufdf_visibility,
                );
//jamesz
           
}
        }
    }


   
/**
     * Get first field for a specific type
     *
     * @param integer Field type ID
     * @param boolean TRUE to prepare user field for correct html displaying
     * @return string or NULL
     */
   
function userfieldget_first_for_type( $type_ID, $prepare_userfield = true )
    {
       
$this->userfields_load();

        if( ! isset(
$this->userfields_by_type[ $type_ID ] ) )
        {
            return
NULL;
        }

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

       
$userfield = $this->userfields[ $idx ];

        if(
$prepare_userfield )
        {    
// Prepare user field for correct html displaying:
           
userfield_prepare( $userfield );
        }

        return
$userfield->uf_varchar;
    }


   
/**
     * Update user data from Request form fields.
     *
     * @param boolean is new user
     * @return mixed true on success, allowed action otherwise
     */
   
function update_from_request( $is_new_user = false )
    {
        global
$current_User, $DB, $Messages, $UserSettings, $Settings, $blog, $admin_url;
        global
$is_api_request;

        if( !
$current_User->can_moderate_user( $this->ID ) && $this->ID != $current_User->ID )
        {
// user is only allowed to update him/herself
           
$Messages->add( T_('You are only allowed to update your own profile!') );
            return
'view';
        }

       
// check if is admin form
       
$is_admin_form = param( 'admin_form', 'boolean', false );

       
// check if is a register finish form:
       
$is_register_finish_form = ( param( 'user_tab', 'string', '' ) == 'register_finish' );

       
// memorize user status ( activated or not )
       
$user_was_activated = $this->check_status( 'is_validated' );
       
$user_old_email = $this->email;

       
// memorize user old login, status and root path, before update
       
$user_old_login = $this->login;
       
$user_root_path = NULL;
       
$FileRootCache = & get_FileRootCache();
        if( !
$is_new_user )
        {
           
$user_FileRoot = & $FileRootCache->get_by_type_and_ID( 'user', $this->ID );
            if(
$user_FileRoot && file_exists( $user_FileRoot->ads_path ) )
            {
               
$user_root_path = $user_FileRoot->ads_path;
            }
        }

        if(
$is_register_finish_form )
        {    
// load data from register form:
           
$load_result = $this->load_from_register_form();
        }
        else
        {    
// load data from request:
           
$load_result = $this->load_from_Request();
        }


        if( !
$load_result )
        {    
// We have found validation errors:
           
if( $is_new_user || ( $is_admin_form && ( $this->ID != 1 ) ) )
            {
// update user status settings but not save
               
$this->update_status_from_Request( false );
            }
            return
'edit';
        }

       
// Update user
       
$DB->begin();

       
$is_password_form = param( 'password_form', 'boolean', false );
        if(
$this->dbsave() )
        {
           
$update_success = true;
            if(
$is_new_user )
            {
               
// Find and inform administrator about other users with same email address on creating new user from back-office:
               
$SQL = new SQL( 'Find other users with same email as new created user' );
               
$SQL->SELECT( 'user_login' );
               
$SQL->FROM( 'T_users' );
               
$SQL->WHERE( 'user_email = '.$DB->quote( $this->get( 'email' ) ) );
               
$SQL->WHERE_and( 'user_ID != '.$DB->quote( $this->ID ) );
               
$same_email_logins = $DB->get_col( $SQL );
                if( ! empty(
$same_email_logins ) )
                {    
// If at least one user exists with same email address:
                   
foreach( $same_email_logins as $l => $same_email_login )
                    {    
// Display a login as link to account profile page:
                       
$same_email_logins[ $l ] = get_user_identity_link( $same_email_login );
                    }
                   
$Messages->add( sprintf( T_('WARNING: the user was created but it shares the same email address as: %s.'), implode( ', ', $same_email_logins ) ), 'warning' );
                }

               
$Messages->add( T_('New user has been created.'), 'success' );
               
report_user_create( $this );
            }
            elseif(
$is_password_form )
            {
               
$Messages->add( T_('Password has been changed.'), 'success' );
            }
            else
            {
                if(
$user_old_login != $this->login && $user_root_path != NULL )
                {
// user login changed and user has a root directory (another way $user_root_path value would be NULL)
                   
$FileRootCache->clear();
                   
$user_FileRoot = & $FileRootCache->get_by_type_and_ID( 'user', $this->ID );
                    if(
$user_FileRoot )
                    {
// user FilerRooot exists, rename user root folder
                       
if( ! @rename( $user_root_path, $user_FileRoot->ads_path ) )
                        {
// unsuccessful folder rename
                           
$Messages->add( sprintf( T_('You cannot choose the new login "%s" (cannot rename user fileroot)'), $this->login), 'error' );
                           
$update_success = false;
                        }
                    }
                }
                if(
$update_success )
                {
                   
$Messages->add( T_('Profile has been updated.'), 'success' );
                   
syslog_insert( sprintf( 'User %s was renamed to %s', '[['.$user_old_login.']]', '[['.$this->login.']]' ), 'info', 'user', $this->ID );
                }
            }

            if( isset(
$this->dbchanges_flags['user_tags'] ) )
            {
// Let's handle the tags:
                //die( var_dump( $this->tags ) );
               
$this->insert_update_user_tags( 'update' );
            }

            if(
$update_success )
            {
               
$user_field_url = $this->get_field_url( true );
                if(
$user_field_url != '' )
                {
// Update url from extra user fields
                   
$this->set( 'url', $user_field_url );
                   
$this->dbsave();
                }

               
$DB->commit();
            }
            else
            {
               
$DB->rollback();
            }

            if(
count( $this->significant_changed_values ) > 0 )
            {
// Send notification email about the changes of user account
               
$this->send_account_changed_notification();
            }

           
// Send notification to owners of lists where user is subscribed/unsubscribed:
           
$this->send_list_owner_notifications();
        }
        elseif(
$is_new_user )
        {    
// Some error on inserting new user in DB:
           
$DB->rollback();
           
$update_success = false;
           
$Messages->add( 'New user creation error', 'error' );
        }
        else
        {    
// Nothing to update:
           
$DB->commit();
           
$update_success = true;
        }

       
// Update user status settings
       
if( ( $is_new_user || ( $is_admin_form && $this->ID != 1 ) ) && ( ! $this->update_status_from_Request( true ) ) )
        {
           
$Messages->add( T_( 'User status couldn\'t be updated!' ), 'error' );
        }

       
$try_to_send_validate_email = ( $is_new_user || $user_was_activated || ( $user_old_email != $this->email ) );
        if(
$update_success && $try_to_send_validate_email && $this->check_status( 'can_be_validated' ) )
        {
// user was deactivated somehow ( or it was just created right now ), check if we need to send validation email
           
if( ( $Settings->get( 'validation_process' ) != 'easy' ) && ( $current_User->ID != $this->ID ) )
            {
// validation process is set to secure, we may send activation email only if this user is the current_User
               
$reg_settings_url = 'href="'.url_add_param( $admin_url, 'ctrl=registration' ).'#fieldset_wrapper_account_activation"';
               
$Messages->add( sprintf( T_( 'Because the Account Activation Process is set to <a %s>Secure</a>, the user will have to request a new activation email next time he tries to log in.' ), $reg_settings_url ), 'note' );
            }
            else
            {
// validation process is easy, send email with permanent activation link
               
if( $this->send_validate_email( NULL, $blog, !$is_new_user && ( $user_old_email != $this->email ) ) ) // $is_new_user check added to prevent regeneration of $reminder_key when creating new user accounts with welcome campaign email
               
{
                    if(
$current_User->ID == $this->ID )
                    {
                       
$redirect_to = NULL;
                        if( ( !
is_admin_page() ) && ( !empty( $blog ) ) )
                        {
// set where to redirect in front office, another way it would go to profile_update.php
                           
$BlogCache = & get_BlogCache();
                           
$Collection = $Blog = $BlogCache->get_by_ID( $blog, false );
                           
$redirect_to = rawurlencode( $Blog->get( 'userprefsurl' ) );
                        }
                       
$activate_info_link = 'href="'.get_activate_info_url( $redirect_to, '&amp;' ).'"';
                       
$Messages->add( sprintf( T_('An email has been sent to your email address (%s). Please click on the link therein to activate your account. <a %s>More info &raquo;</a>' ), $this->dget('email'), $activate_info_link ), 'success' );
                    }
                    else
                    {
                       
$Messages->add( sprintf( T_('An account activation email has been sent to %s' ), $this->login ), 'success' );
                    }
                }
                elseif( isset(
$demo_mode ) && $demo_mode )
                {
                   
$Messages->add( 'Could not send activation email. Sending emails is disabled in demo mode.', 'note' );
                }
            }
        }

       
// Update user settings:
       
$is_preferences_form = param( 'preferences_form', 'boolean', false );
       
$is_subscriptions_form = param( 'subscriptions_form', 'boolean', false );
        if(
$is_preferences_form || $is_subscriptions_form || $is_admin_form )
        {
// Update UserSettings
           
if( $UserSettings->dbupdate() && ( $is_preferences_form || $is_subscriptions_form ) )
            {
// Show feature settings update successful message on user preferences form
               
$Messages->add( T_('User feature settings have been changed.'), 'success');
            }
        }

        return
true;
    }


   
/**
     * Handle close user account request and update account close reason
     *
     * @param boolean save modifications or not. User false only in case when User and UserSettings object will be saved later.
     * @return boolean true on success, false otherwise
     */
   
function update_status_from_Request( $dbsave, $new_status = NULL )
    {
        global
$DB, $Settings, $UserSettings, $Messages, $current_User, $servertimenow;
        global
$is_api_request;

        if( !
is_logged_in() || ! $current_User->can_moderate_user( $this->ID ) && ! ( $Settings->get( 'account_close_enabled' ) && $current_User->ID == $this->ID ) )
        {    
// Only moderators can update user status:
           
$Messages->add_to_group( T_( 'You do not have permission to perform this action' ), 'error', T_('Close account').':' );
            return
false;
        }

        if(
$dbsave )
        {
// save required
           
$DB->begin();
        }

       
$edited_user_status = param( 'edited_user_status', 'string' );
        if( !
$is_api_request || ( $is_api_request && isset( $edited_user_status ) ) )
        {
            if( empty(
$new_status ) )
            {
               
$new_status = param( 'edited_user_status', 'string', true );
            }

           
// a close reason type thst is selected from list
           
$account_close_type = param( 'account_close_type', 'string', '' );
           
// a close reason text - max 255 characters
           
$account_close_reason = substr( param( 'account_close_reason', 'text', '' ), 0, 255 );
            if( ( !
$this->check_status( 'is_closed' ) ) && ( $new_status == 'closed' ) )
            {
// account was not closed yet
               
if( empty( $account_close_reason ) && empty( $account_close_type ) )
                {
// Default value of reason
                   
$account_close_reason = T_( 'Other' );
                }
               
$this->set( 'status', 'closed' );
               
$UserSettings->set( 'account_close_ts', $servertimenow, $this->ID );
               
$UserSettings->set( 'account_close_reason', trim( $account_close_type.' '.$account_close_reason ), $this->ID );
               
// delete last activation email data, this user must not be allowed to reactivate the account ( only admin users may change the status again )
               
$UserSettings->delete( 'last_activation_reminder_key', $this->ID );
               
$UserSettings->delete( 'last_activation_email', $this->ID );
               
// create query to clear all session's of the user
               
$clear_sessions_query = 'UPDATE T_sessions
                                    SET sess_key = NULL
                                    WHERE sess_user_ID = '
.$DB->quote( $this->ID );
               
// unsubscribe user from all lists:
               
$subscribed_newletter_IDs = $this->get_newsletter_subscriptions( 'subscribed' );
               
$this->unsubscribe( $subscribed_newletter_IDs, array( 'user_account_closed' => true ) );

                if(
$dbsave && $this->dbupdate() && $UserSettings->dbupdate() && ( $DB->query( $clear_sessions_query ) !== false ) )
                {
// all db modification was successful
                   
$DB->commit();
                    if(
$current_User->ID != $this->ID )
                    {    
// If admin closed some user account
                        // Send notification email about closed account to users with edit users permission
                       
$email_template_params = array(
                               
'login'           => $this->login,
                               
'email'           => $this->email,
                               
'reason'          => $account_close_reason,
                               
'user_ID'         => $this->ID,
                               
'closed_by_admin' => $current_User->get_username(),
                               
'days_count'      => $this->get_days_count_close()
                            );
                       
send_admin_notification( NT_('User account closed'), 'account_closed', $email_template_params );
                    }

                   
// Send notification to owners of list where user was automatically unsubscribed:
                   
$this->send_list_owner_notifications( 'unsubscribe' );

                    return
true;
                }
            }
            else
            {
               
// set status
               
$this->set( 'status', $new_status );
               
$UserSettings->set( 'account_close_reason', $account_close_reason, $this->ID );
                if(
$dbsave && $this->dbupdate() )
                {
// db update
                   
$UserSettings->dbupdate();
                   
$DB->commit();
                    return
true;
                }
            }

            if(
$dbsave )
            {
// save was required, but wasn't successful
               
$DB->rollback();
                return
false;
            }
        }

        return
true;
    }


   
/**
     * Update profileupdate date. Call after a publicly visible user property was updated.
     */
   
function set_profileupdate_date()
    {
        global
$current_User, $localtimenow;
        if( ( !empty(
$current_User ) ) && ( $this->ID == $current_User->ID ) )
        {
           
$this->set( 'profileupdate_date', date( 'Y-m-d H:i:s', $localtimenow ) );
        }
    }


   
/**
     * Update user avatar file
     *
     * @param integer the new avatar file ID
     * @param boolean TRUE to restore this file
     * @return mixed true on success, allowed action otherwise
     */
   
function update_avatar( $file_ID, $restore = false )
    {
        global
$current_User, $Messages;

       
$can_moderate_user = $current_User->can_moderate_user( $this->ID );
        if( !
$can_moderate_user && ( $restore || $this->ID != $current_User->ID ) )
        {
// user is only allowed to update him/herself
           
$Messages->add( T_('You are not allowed to update this profile!'), 'error' );
            return
'view';
        }

        if(
$file_ID == NULL )
        {
           
$Messages->add( T_('Profile picture could not be changed!'), 'error' );
            return
'edit';
        }

       
$FileCache = & get_FileCache();
       
$File = $FileCache->get_by_ID( $file_ID );

        if( !
$File->get( 'can_be_main_profile' ) && ! $can_moderate_user )
        {
// Deny to restore picture if current user has no perm
           
$Messages->add( T_('Profile picture could not be changed!'), 'error' );
            return
'edit';
        }

        if(
$File->_FileRoot->type == 'user' && $File->_FileRoot->in_type_ID != $this->ID )
        {
// don't allow to use pictures from other users
           
$Messages->add( T_('Profile picture could not be changed!'), 'error' );
            return
'edit';
        }

       
$restored_success = false;
        if(
$restore && $can_moderate_user )
        {
// Restore profile picture
           
$File->set( 'can_be_main_profile', '1' );
           
$restored_success = $File->dbupdate();
        }

       
$this->set( 'avatar_file_ID', $file_ID, true );
       
// update profileupdate_date, because a publicly visible user property was changed
       
$this->set_profileupdate_date();
       
$this->dbupdate();

        if(
$restored_success )
        {
           
$Messages->add( T_('Profile picture has been restored.'), 'success' );
        }
        else
        {
           
$Messages->add( T_('Profile picture has been changed.'), 'success' );
        }

       
// Send notification email about the changes of user account
       
$this->send_account_changed_notification( true );

        return
true;
    }


   
/**
     * Get the rotate avatar icons
     *
     * @param integer File ID
     * @param array Params
     * @return string HTML text with 3 icons to rotate avatar
     */
   
function get_rotate_avatar_icons( $file_ID, $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'   => '<br />',
               
'after'    => '',
               
'text'     => '',
               
'user_tab' => 'avatar',
            ),
$params );

       
// Init links to rotate avatar
       
if( is_admin_page() )
        {
// Back-office
           
$url_rotate_90_left = regenerate_url( '', 'user_tab='.$params['user_tab'].'&user_ID='.$this->ID.'&action=rotate_avatar_90_left&file_ID='.$file_ID.'&'.url_crumb( 'user' ), '', '&' );
           
$url_rotate_180 = regenerate_url( '', 'user_tab='.$params['user_tab'].'&user_ID='.$this->ID.'&action=rotate_avatar_180&file_ID='.$file_ID.'&'.url_crumb( 'user' ), '', '&') ;
           
$url_rotate_90_right = regenerate_url( '', 'user_tab='.$params['user_tab'].'&user_ID='.$this->ID.'&action=rotate_avatar_90_right&file_ID='.$file_ID.'&'.url_crumb( 'user' ), '', '&' );
        }
        else
        {
// Front-office
           
global $Collection, $Blog;
           
$url_rotate_90_left = get_htsrv_url().'profile_update.php?user_tab='.$params['user_tab'].'&blog='.$Blog->ID.'&user_ID='.$this->ID.'&action=rotate_avatar_90_left&file_ID='.$file_ID.'&'.url_crumb( 'user' );
           
$url_rotate_180 = get_htsrv_url().'profile_update.php?user_tab='.$params['user_tab'].'&blog='.$Blog->ID.'&user_ID='.$this->ID.'&action=rotate_avatar_180&file_ID='.$file_ID.'&'.url_crumb( 'user' );
           
$url_rotate_90_right = get_htsrv_url().'profile_update.php?user_tab='.$params['user_tab'].'&blog='.$Blog->ID.'&user_ID='.$this->ID.'&action=rotate_avatar_90_right&file_ID='.$file_ID.'&'.url_crumb( 'user' );
        }

       
$html = $params['before'];

       
$html .= action_icon( T_('Rotate this picture 90&deg; to the left'), 'rotate_left', $url_rotate_90_left, '', 0, 0, array( 'style' => 'margin-right:4px' ) );
       
$html .= action_icon( T_('Rotate this picture 180&deg;'), 'rotate_180', $url_rotate_180, '', 0, 0, array( 'style' => 'margin-right:4px' ) );
       
$html .= action_icon( T_('Rotate this picture 90&deg; to the right'), 'rotate_right', $url_rotate_90_right, $params['text'], empty( $params['text'] ) ? 0: 3, empty( $params['text'] ) ? 0: 4 );

       
$html .= $params['after'];

        return
$html;
    }


   
/**
     * Get file object by ID of this user
     *
     * @param integer Avatar file ID
     * @param string Error code on denied action, It is updated by reference:
     *        'only_own_profile' - User can update only own profile
     *        'wrong_file'       - Request with wrong file ID
     *        'other_user'       - Restricted to edit files from other users
     * @return object|boolean File object on success, FALSE on failed
     */
   
function & get_File_by_ID( $file_ID, & $error_code )
    {
        global
$current_User, $Messages;

       
$error_code = false;

       
$can_moderate_user = $current_User->can_moderate_user( $this->ID );
        if( !
$can_moderate_user && $this->ID != $current_User->ID )
        {
// user is only allowed to update him/herself
           
$Messages->add( T_('You are only allowed to update your own profile!'), 'error' );
           
$error_code = 'only_own_profile';
           
$r = false;
            return
$r;
        }

        if( empty(
$file_ID ) )
        {
// File ID is empty
           
$Messages->add( T_('You did not specify a file.'), 'error' );
           
$error_code = 'wrong_file';
           
$r = false;
            return
$r;
        }

       
$FileCache = & get_FileCache();
       
$File = & $FileCache->get_by_ID( $file_ID, false, false );
        if( empty(
$File ) )
        {
// File does't exist
           
$Messages->add( T_('The requested file does not exist!'), 'error' );
           
$error_code = 'wrong_file';
           
$r = false;
            return
$r;
        }

        if(
$File->_FileRoot->type != 'user' || ( $File->_FileRoot->in_type_ID != $this->ID && ! $can_moderate_user ) )
        {
// don't allow use the pictures from other users
           
$Messages->add( T_('The requested file doesn\'t belong to this user.'), 'error' );
           
$error_code = 'other_user';
           
$r = false;
            return
$r;
        }

        return
$File;
    }


   
/**
     * Rotate user avatar file
     *
     * @param integer the new avatar file ID
     * @param integer Degrees to rotate
     * @return object|string File object on success;
     *         Error code on denied action:
     *          'only_own_profile' - User can update only own profile
     *          'wrong_file'       - Request with wrong file ID
     *          'other_user'       - Restricted to edit files from other users
     *          'rotate_error'     - Some errors in rotate function
     */
   
function rotate_avatar( $file_ID, $degrees )
    {
       
$File = & $this->get_File_by_ID( $file_ID, $error_code );
        if( !
$File )
        {
// The file cannot be used for this user, return error code
           
return $error_code;
        }

        global
$Messages;
       
load_funcs( 'files/model/_image.funcs.php' );

        if( !
rotate_image( $File, $degrees ) )
        {
// Some errors were during rotate the avatar
           
$Messages->add( T_('Profile picture could not be rotated!'), 'error' );
            return
'rotate_error';
        }

       
$Messages->add( T_('Profile picture has been rotated.'), 'success' );
        return
true;
    }


   
/**
     * Rotate user avatar file
     *
     * @param integer the new avatar file ID
     * @param integer X coordinate (in percents)
     * @param integer Y coordinate (in percents)
     * @param integer Width (in percents)
     * @param integer Height (in percents)
     * @return object|string File object on success;
     *         Error code on denied action:
     *          'only_own_profile' - User can update only own profile
     *          'wrong_file'       - Request with wrong file ID
     *          'other_user'       - Restricted to edit files from other users
     *          'crop_error'       - Some errors in crop function
     */
   
function crop_avatar( $file_ID, $x, $y, $width, $height )
    {
       
$File = & $this->get_File_by_ID( $file_ID, $error_code );
        if( !
$File )
        {
// The file cannot be used for this user, return error code
           
return $error_code;
        }

        global
$Messages, $Settings;
       
load_funcs( 'files/model/_image.funcs.php' );

        if( !
crop_image( $File, $x, $y, $width, $height, intval( $Settings->get( 'min_picture_size' ) ), 1024 ) )
        {
// Some errors were during rotate the avatar
           
$Messages->add( T_('Profile picture could not be cropped!'), 'error' );
            return
'crop_error';
        }

       
$Messages->add( T_('Profile picture has been cropped.'), 'success' );
        return
true;
    }


   
/**
     * Get the crop avatar icon
     *
     * @param integer File ID
     * @param array Params
     * @return string HTML text with icon to crop avatar
     */
   
function get_crop_avatar_icon( $file_ID, $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'before'   => '',
               
'after'    => '',
               
'text'     => '',
               
'user_tab' => 'avatar',
               
'onclick'  => '',
            ),
$params );

       
// Init links to rotate avatar
       
if( is_admin_page() )
        {
// Back-office
            //$url_crop = regenerate_url( 'user_tab', 'user_tab=crop&user_ID='.$this->ID.'&file_ID='.$file_ID.'&'.url_crumb( 'user' ), '', '&' );
           
$url_crop = '#';
        }
        else
        {
// Front-office
           
global $Collection, $Blog;
           
$url_crop = url_add_param( $Blog->gen_blogurl(), 'disp=avatar&action=crop&file_ID='.$file_ID );
        }

       
$html = $params['before'];

       
$link_params = array();
        if( ! empty(
$params['onclick'] ) )
        {
           
$link_params['onclick'] = $params['onclick'];
        }
       
$html .= action_icon( T_('Crop this picture'), 'crop', $url_crop, $params['text'], ( empty( $params['text'] ) ? 0: 3 ), ( empty( $params['text'] ) ? 0: 4 ), $link_params );

       
$html .= $params['after'];

        return
$html;
    }


   
/**
     * Remove user avatar
     *
     * @return mixed true on success, false otherwise
     */
   
function remove_avatar( $forbid = false )
    {
        global
$current_User, $Messages;

       
$can_moderate_user = $current_User->can_moderate_user( $this->ID );
        if( !
$can_moderate_user && ( $forbid || $this->ID != $current_User->ID ) )
        {
// user is only allowed to update him/herself
           
$Messages->add( T_('You are not allowed to update this profile!'), 'error' );
            return
false;
        }

       
$forbidden_success = false;
        if(
$forbid && $can_moderate_user )
        {
// Forbid profile picture
           
$forbidden_file_ID = $this->get( 'avatar_file_ID' );
           
$FileCache = & get_FileCache();
            if( ! (
$forbidden_File = & $FileCache->get_by_ID( $forbidden_file_ID, false, false ) ) )
            {
// Broken avatar file
               
$Messages->add( T_('Profile picture is not found!'), 'error' );
                return
false;
            }

           
$forbidden_File->set( 'can_be_main_profile', '0' );
           
$forbidden_success = $forbidden_File->dbupdate();
        }

       
$this->set( 'avatar_file_ID', NULL, true );
       
$this->dbupdate();

        if(
$forbidden_success )
        {
           
$Messages->add( T_('Profile picture has been forbidden.'), 'success' );
        }
        else
        {
           
$Messages->add( T_('Profile picture has been removed.'), 'success' );
        }

        return
true;
    }


   
/**
     * Delete user avatar file
     *
     * @param integer the avatar file ID
     * @return mixed true on success, allowed action otherwise
     */
   
function delete_avatar( $file_ID )
    {
        global
$current_User, $Messages;

        if( !
$current_User->can_moderate_user( $this->ID ) && $this->ID != $current_User->ID )
        {
// user is only allowed to update him/herself
           
$Messages->add( T_('You are only allowed to update your own profile!'), 'error' );
            return
'view';
        }

        if(
$file_ID == NULL )
        {
           
$Messages->add( T_('Profile picture could not be changed!'), 'error' );
            return
'edit';
        }

       
$LinkOwner = new LinkUser( $this );
       
$Link = & $LinkOwner->get_link_by_file_ID( $file_ID );
        if(
$Link )
        {
           
$File = $Link->get_File();
           
$LinkOwner->remove_link( $Link );
        }
        else
        {
           
$FileCache = & get_FileCache();
           
$File = $FileCache->get_by_ID( $file_ID );
        }

        if(
$file_ID == $this->avatar_file_ID )
        {    
// Unset this picture from user avatar if it is used as main picture
           
$this->set( 'avatar_file_ID', NULL, true );
           
$this->dbupdate();
        }

        if( (
$File->_FileRoot->type == 'user' ) && ( $File->_FileRoot->in_type_ID == $this->ID )
            && (
strpos( $File->get_rdfp_rel_path(), 'profile_pictures' ) === 0 ) )
        {
// Delete the file if it is in the current User profile_pictures folder
           
$File->unlink();
        }

       
$Messages->add( T_('Profile picture has been deleted.'), 'success' );
        return
true;
    }


   
/**
     * Update user avatar file to the currently uploaded file
     *
     * @return mixed true on success, allowed action otherwise.
     */
   
function update_avatar_from_upload()
    {
        global
$current_User, $Messages, $Settings;

        if( !
$current_User->can_moderate_user( $this->ID ) && $this->ID != $current_User->ID )
        {
// user is only allowed to update him/herself
           
$Messages->add( T_('You are only allowed to update your own profile!'), 'error' );
            return
'view';
        }

       
// process upload
       
$FileRootCache = & get_FileRootCache();
       
$root = FileRoot::gen_ID( 'user', $this->ID );
       
$result = process_upload( $root, 'profile_pictures', true, false, true, false, $Settings->get( 'min_picture_size' ) );
        if( empty(
$result ) )
        {
           
$Messages->add( T_( 'You don\'t have permission to selected user file root.' ), 'error' );
            return
'view';
        }

       
$uploadedFiles = $result['uploadedFiles'];
        if( !empty(
$uploadedFiles ) )
        {
// upload was successful
           
$File = $uploadedFiles[0];
           
$duplicated_files = $File->get_duplicated_files( array( 'root_ID' => $this->ID ) );
            if( ! empty(
$duplicated_files ) )
            {
// The file is the duplicate of other profile picture, we should delete it
               
$File->dbdelete();
               
$Messages->add( T_( 'It seems you are trying to upload the same profile picture twice.' ), 'error' );
                return
'edit';
            }
            elseif(
$File->is_image() )
            {
// uploaded file is an image
               
$LinkOwner = new LinkUser( $this );
               
$File->link_to_Object( $LinkOwner );
               
$avatar_changed = false;
                if( empty(
$this->avatar_file_ID ) )
                {
// set uploaded image as avatar
                   
$this->set( 'avatar_file_ID', $File->ID, true );
                   
// update profileupdate_date, because a publicly visible user property was changed
                   
$this->set_profileupdate_date();
                   
$this->dbupdate();
                   
$avatar_changed = true;
                   
$Messages->add( T_('Profile picture has been changed.'), 'success' );
                }
                else
                {
// User already has the avatar
                   
$Messages->add( T_('New picture has been uploaded.'), 'success' );
                }
               
// Clear previous Links to load new uploaded file
               
$LinkOwner->clear_Links();
               
// Send notification email about the changes of user account
               
$this->send_account_changed_notification( $avatar_changed, $File->ID );
                return
true;
            }
            else
            {
// uploaded file is not an image, delete the file
               
$Messages->add( T_( 'The file you uploaded does not seem to be an image.' ) );
               
$File->unlink();
            }
        }

       
$failedFiles = $result['failedFiles'];
        if( !empty(
$failedFiles ) )
        {
           
$Messages->add( $failedFiles[0] );
        }

        return
'edit';
    }


   
/**
     * Get session param from the user last session
     *
     * @param string param name
     * @return mixed param value
     */
   
function get_last_session_param( $parname )
    {
        global
$DB;

       
$parname = 'sess_'.$parname;
       
$query = 'SELECT sess_ID, '.$parname.'
                    FROM T_sessions
                    WHERE sess_user_ID = '
.$this->ID.'
                    ORDER BY sess_ID DESC
                    LIMIT 1'
;
       
$result = $DB->get_row( $query );
        if( !empty(
$result ) )
        {
            return
format_to_output( $result->$parname );
        }

        return
NULL;
    }


   
/**
     * Send a welcome private message
     */
   
function send_welcome_message()
    {
        global
$Settings, $UserSettings;

        if( !
$Settings->get( 'welcomepm_enabled' ) )
        {    
// Sending of welcome PM is disabled
           
return false;
        }

        if(
$Settings->get( 'welcomepm_notag' ) )
        {    
// Don't send welcome PM if user account already has an user tag:
           
$user_tags = $this->get_usertags();
            if( ! empty(
$user_tags ) )
            {
                return
false;
            }
        }

        if(
$UserSettings->get( 'welcome_message_sent', $this->ID ) )
        {    
// User already received the welcome message
           
return false;
        }

        if( !
$this->accepts_pm() )
        {    
// user can't read private messages, or doesn't want to receive private messages
           
return false;
        }

       
// Check sender user login for existing
       
$UserCache = & get_UserCache();
       
$User = $UserCache->get_by_login( $Settings->get( 'welcomepm_from' ) );
        if( !
$User )
        {    
// Don't send an welcome email if sender login is incorrect
           
return false;
        }

       
load_class( 'messaging/model/_thread.class.php', 'Thread' );
       
load_class( 'messaging/model/_message.class.php', 'Message' );

       
// Insert new thread:
       
$edited_Thread = new Thread();
       
$edited_Message = new Message();
       
$edited_Message->Thread = & $edited_Thread;
       
$edited_Message->Thread->set( 'title', $Settings->get( 'welcomepm_title' ) );
       
$edited_Message->Thread->recipients_list = array( $this->ID );
       
$edited_Message->set( 'author_user_ID', $User->ID );
       
$edited_Message->creator_user_ID = $User->ID;
       
$edited_Message->set( 'text', $Settings->get( 'welcomepm_message' ) );
        if(
$edited_Message->dbinsert_individual( $User ) )
        {    
// The welcome message was sent/created successfully
            // Change user setting to TRUE in order to don't send it twice
           
$UserSettings->set( 'welcome_message_sent', 1, $this->ID );
           
$UserSettings->dbupdate();
        }
    }


   
/**
     * Update notification sender's info from General settings
     *
     * @param boolean TRUE - to force an updating of the fields
     */
   
function update_sender( $force_update = false )
    {
        global
$Settings, $UserSettings;

        if(
$force_update || $UserSettings->get( 'notification_sender_email', $this->ID ) == '' )
        {
// Update sender's email
           
$UserSettings->set( 'notification_sender_email', $Settings->get( 'notification_sender_email' ), $this->ID );
        }
        if(
$force_update || ( ( $UserSettings->get( 'notification_sender_name', $this->ID ) == '' ) && ( $Settings->get( 'notification_sender_name' ) != 'b2evo mailer' ) ) )
        {
// Update sender's name if it was not set and the general settings has different value then the default
           
$UserSettings->set( 'notification_sender_name', $Settings->get( 'notification_sender_name' ), $this->ID );
        }

       
$UserSettings->dbupdate();
    }


   
/**
     * Get all own blogs of this user which current user can delete
     *
     * @return array Blogs
     */
   
function get_deleted_blogs()
    {
        global
$DB;

       
// Get all own blogs of the edited user
       
$BlogCache = & get_BlogCache();
       
$BlogCache->ID_array = array();
       
$user_Blogs = $BlogCache->load_where( 'blog_owner_user_ID = '.$DB->quote( $this->ID ) );

       
$deleted_Blogs = array();
        foreach(
$user_Blogs as $user_Blog )
        {
            if(
check_user_perm( 'blog_properties', 'edit', false, $user_Blog->ID ) )
            {    
// Current user has a permission to delete this blog
               
$deleted_Blogs[] = $user_Blog;
            }
        }

        return
$deleted_Blogs;
    }


   
/**
     * Delete all blogs of the user recursively
     *
     * @return boolean True on success
     */
   
function delete_blogs()
    {
        global
$DB, $UserSettings;

       
$DB->begin();

       
// Get all own blogs of this user which current user can delete
       
$deleted_Blogs = $this->get_deleted_blogs();

        foreach(
$deleted_Blogs as $deleted_Blog )
        {
           
// Delete from DB:
           
$deleted_Blog->dbdelete();

           
set_working_blog( 0 );
           
$UserSettings->delete( 'selected_blog' );    // Needed or subsequent pages may try to access the delete blog
           
$UserSettings->dbupdate();
        }

       
$DB->commit();

        return
true;
    }


   
/**
     * Get the posts of this user which current user can delete
     *
     * @param string Type of the deleted posts
     *               'created'        - the posts created by this user
     *               'edited'         - the posts edited by this user
     *               'created|edited' - the posts created OR edited by this user
     * @return array Items
     */
   
function get_deleted_posts( $type, $check_perm = true )
    {
        global
$DB;

       
$ItemCache = & get_ItemCache();
       
$ItemCache->ID_array = array();
        switch(
$type )
        {
            case
'created':
               
$user_Items = $ItemCache->load_where( 'post_creator_user_ID = '.$DB->quote( $this->ID ) );
                break;

            case
'edited':
               
$user_Items = $ItemCache->load_where( 'post_lastedit_user_ID = '.$DB->quote( $this->ID ) );
                break;

            case
'created|edited':
               
$user_Items = $ItemCache->load_where( 'post_lastedit_user_ID = '.$DB->quote( $this->ID ).' OR post_creator_user_ID = '.$DB->quote( $this->ID ) );
                break;
        }

       
$deleted_Items = array();
        foreach(
$user_Items as $user_Item )
        {
            if( ( !
$check_perm ) || check_user_perm( 'item_post!CURSTATUS', 'delete', false, $user_Item ) )
            {    
// Current user has a permission to delete this item
               
$deleted_Items[] = $user_Item;
            }
        }

        return
$deleted_Items;
    }


   
/**
     * Get the posts of this user which current user can delete
     *
     * @param string Type of the deleted posts
     *               'created'        - the posts created by this user
     *               'edited'         - the posts edited by this user
     *               'created|edited' - the posts created OR edited by this user
     * @param boolean Count only the number of posts that the current user can delete
     * @return mixed array of Items if $count_only is FALSE otherwise integer
     */
   
function get_deleted_posts2( $type, $count_only = false )
    {
        global
$DB, $current_User;

       
$current_User_Group = $current_User->get_Group();

        switch(
$type )
        {
            case
'created':
               
$where_clause = 'post_creator_user_ID = '.$DB->quote( $this->ID );
                break;

            case
'edited':
               
$from_add = 'LEFT JOIN ( SELECT iver_itm_ID, COUNT(*) AS counter
                        FROM T_items__version
                        WHERE iver_edit_user_ID = '
.$DB->quote( $this->ID ).'
                        GROUP BY iver_itm_ID ) AS b
                            ON b.iver_itm_ID = post_ID '
;
               
$where_clause = 'post_creator_user_ID != '.$DB->quote( $this->ID ).' AND ( b.counter > 0 OR post_lastedit_user_ID = '.$DB->quote( $this->ID ).' )';
                break;

            case
'created|edited':
               
$where_clause = '( post_lastedit_user_ID = '.$DB->quote( $this->ID ).' OR post_creator_user_ID = '.$DB->quote( $this->ID ).' )';
                break;
        }

        if(
$count_only )
        {
           
$sql = 'SELECT COUNT( DISTINCT( post_ID ) ) ';
        }
        else
        {
           
$sql = 'SELECT DISTINCT T_items__item.* ';
        }

       
$sql .= 'FROM T_items__item ';
        if( ! empty(
$from_add ) )
        {
           
$sql .= $from_add;
        }
       
$sql .= 'LEFT JOIN (
                    SELECT postcat_post_ID,
                        COUNT( * ) AS categories,
                        SUM( IF( cat_lock = 1, 1, 0 ) ) AS locked_categories
                    FROM T_postcats
                    LEFT JOIN T_categories
                        ON cat_ID = postcat_cat_ID
                    GROUP BY postcat_post_ID
                ) AS a
                    ON a.postcat_post_ID = post_ID
                LEFT JOIN T_postcats AS b
                    ON b.postcat_post_ID = post_ID
                LEFT JOIN T_categories
                    ON cat_ID = b.postcat_cat_ID
                LEFT JOIN T_blogs
                    ON blog_ID = cat_blog_ID
                LEFT JOIN T_coll_user_perms
                    ON bloguser_blog_ID = blog_ID AND bloguser_user_ID = '
.$DB->quote( $current_User->ID ).'
                LEFT JOIN T_coll_group_perms
                    ON bloggroup_blog_ID = blog_ID AND bloggroup_group_ID = '
.$DB->quote( $current_User_Group->ID ).'
                LEFT JOIN T_groups__groupsettings
                    ON gset_grp_ID = bloggroup_group_ID AND gset_name = "perm_users"
                LEFT JOIN (
                    SELECT
                        bloggroup_blog_ID,
                        SUM( IF( bloggroup_perm_delpost = 1, 1, 0 ) ) AS secondary_grp_perm_delpost,
                        SUM( IF( bloggroup_perm_cats = 1, 1, 0 ) ) AS secondary_grp_perm_cats
                    FROM T_users__secondary_user_groups
                    LEFT JOIN T_groups
                        ON sug_grp_ID = grp_ID
                    LEFT JOIN T_coll_group_perms
                        ON bloggroup_group_ID = grp_ID
                    WHERE
                        sug_user_ID = '
.$DB->quote( $current_User->ID ).'
                    GROUP BY
                        bloggroup_blog_ID
                ) AS sg
                    ON sg.bloggroup_blog_ID = blog_ID
                WHERE
                    '
.$where_clause.'
                    AND
                    (
                       ( categories > locked_categories OR ( bloguser_perm_cats = 1 OR bloggroup_perm_cats = 1 OR secondary_grp_perm_cats > 0 ) )
                        AND
                        (
                            ( blog_advanced_perms = 0 && blog_owner_user_ID = '
.$DB->quote( $current_User->ID ).' )
                            OR
                            ( blog_advanced_perms = 1 && ( bloguser_perm_delpost = 1 OR bloggroup_perm_delpost = 1 OR secondary_grp_perm_delpost > 0 ) )
                        )
                        OR
                        ( gset_value = "edit" )
                    )'
;

        if(
$count_only )
        {
            return
$DB->get_var( $sql );
        }
        else
        {
           
$user_Items = $DB->get_results( $sql );

           
$deleted_Items = array();
            foreach(
$user_Items as $r => $row )
            {
               
$deleted_Items[] = new Item( $row );
            }

            return
$deleted_Items;
        }
    }


   
/**
     * Delete posts of the user
     *
     * @param string Type of the deleted posts
     *               'created'        - the posts created by this user
     *               'edited'         - the posts edited by this user
     *               'created|edited' - the posts created OR edited by this user
     * @return boolean True on success
     */
   
function delete_posts( $type )
    {
        global
$DB, $Plugins, $current_User;

       
$DB->begin();

       
// Get the posts of this user which current user can delete.
        // Note: If current user can moderate this user then it is allowed to delete all user data even if it wouldn't be allowed otherwise.
       
$check_perm = ( ( $type != 'created' ) || ( ! $current_User->can_moderate_user( $this->ID ) ) );
       
$deleted_Items = $this->get_deleted_posts( $type, $check_perm );

        foreach(
$deleted_Items as $deleted_Item )
        {
           
$Plugins->trigger_event( 'AdminBeforeItemEditDelete', array( 'Item' => & $deleted_Item ) );

           
// Delete from DB:
           
$deleted_Item->dbdelete();
        }

       
$DB->commit();

        return
true;
    }


   
/**
     * Get the comments IDs that were created by this user
     *
     * @return array IDs of the own comments
     */
   
function get_own_comments_IDs()
    {
        global
$DB;

       
// Get the comments of the user
       
$SQL = new SQL( 'Get the comments of user #'.$this->ID );
       
$SQL->SELECT( 'comment_ID' );
       
$SQL->FROM( 'T_comments' );
       
$SQL->WHERE( 'comment_author_user_ID = '.$DB->quote( $this->ID ) );

        return
$DB->get_col( $SQL );
    }


   
/**
     * Check if current has at least one own comment to delete
     *
     * @return array Comments IDs
     */
   
function has_comment_to_delete()
    {
        global
$current_User;

       
$comments_IDs = $this->get_own_comments_IDs();

        if( !
count( $comments_IDs ) )
        {
// User has no comments
           
return false;
        }

        if(
$current_User->can_moderate_user( $this->ID ) )
        {
// If current user can moderate this user then it is allowed to delete all user data even if it wouldn't be allowed otherwise.
           
return true;
        }

       
$CommentCache = & get_CommentCache();

        foreach(
$comments_IDs as $comment_ID )
        {
            if( (
$user_Comment = & $CommentCache->get_by_ID( $comment_ID, false, false ) ) &&
                 
check_user_perm( 'comment!CURSTATUS', 'delete', false, $user_Comment ) )
            {
// Current user has a permission to delete this comment
               
return true;
            }
           
// Clear a cache to avoid a memory allocation error
           
$CommentCache->clear();
        }

        return
false;
    }


   
/**
     * Delete comments of the user
     *
     * @return boolean True on success
     */
   
function delete_comments()
    {
        global
$DB, $current_User;

       
// If user has a huge amount of the comments it will takes many time to delete all comments
       
set_max_execution_time( 900 );

       
$DB->begin();

       
// Get the comments of this user which current user can delete
       
$comments_IDs = $this->get_own_comments_IDs();

        if( !
count( $comments_IDs ) )
        {
// User has no comments
           
return false;
        }

       
$CommentCache = & get_CommentCache();
       
$ItemCache = & get_ItemCache();

       
// If current user can moderate this user then it is allowed to delete all user data even if it wouldn't be allowed otherwise.
       
$current_user_can_moderate = $current_User->can_moderate_user( $this->ID );

       
$result = false;
        foreach(
$comments_IDs as $comment_ID )
        {
           
$deleted_Comment = & $CommentCache->get_by_ID( $comment_ID, false, false );
            if(
$deleted_Comment &&
                (
$current_user_can_moderate ||
                 
check_user_perm( 'comment!CURSTATUS', 'delete', false, $deleted_Comment ) ) )
            {
// Current user has a permission to delete this comment
                // Delete from DB
               
$result = $deleted_Comment->dbdelete( true, false );
                if( !
$result )
                {
                    break;
                }
            }
           
// Clear a cache to avoid a memory allocation error
           
$CommentCache->clear();
           
$ItemCache->clear();
        }

        if(
$result )
        {
           
$DB->commit();
        }
        else
        {
           
$DB->rollback();
        }

        return
$result;
    }


   
/**
     * Delete private messaged of the user
     *
     * @param string Type ( sent | received )
     * @return boolean True on success
     */
   
function delete_messages( $type = 'sent' )
    {
        global
$DB, $Plugins, $current_User;

       
// Check permissions
        // Note: If current user can moderate this user then it is allowed to delete all user data even if it wouldn't be allowed otherwise
       
if( ! $current_User->can_moderate_user( $this->ID ) )
        {
// Note: if users have delete messaging perms then they can delete any user messages ( Of course only if the delete action is available/displayed for them )
           
check_user_perm( 'perm_messaging', 'delete', true );
        }

       
$DB->begin();

       
$MessageCache = & get_MessageCache();
       
$MessageCache->clear();
        if(
$type == 'received' )
        {    
// Received messages:
           
$SQL = $MessageCache->get_SQL_object();
           
$SQL->FROM_add( 'INNER JOIN T_messaging__threadstatus ON tsta_thread_ID = msg_thread_ID' );
           
$SQL->WHERE( 'tsta_user_ID = '.$DB->quote( $this->ID ) );
           
$SQL->WHERE_and( 'msg_author_user_ID != '.$DB->quote( $this->ID ) );
           
$MessageCache->load_by_sql( $SQL );
        }
        else
        {    
// Sent messages:
           
$MessageCache->load_where( 'msg_author_user_ID = '.$this->ID );
        }

       
$thread_IDs = array();
        while( (
$iterator_Message = & $MessageCache->get_next() ) != NULL )
        {
// Iterate through MessageCache
            // Delete a message from DB:
           
$msg_thread_ID = $iterator_Message->get( 'thread_ID' );
            if(
$iterator_Message->dbdelete() )
            {
               
$thread_IDs[ $msg_thread_ID ] = NULL;
            }
        }

        if( ! empty(
$thread_IDs ) )
        {    
// Delete read statuses of the threads where message was deleted:
           
$DB->query( 'DELETE FROM T_messaging__threadstatus
                WHERE tsta_user_ID = '
.$DB->quote( $this->ID ).'
                  AND tsta_thread_ID IN ( '
.$DB->quote( array_keys( $thread_IDs ) ).' )' );
        }

       
$DB->commit();

        return
true;
    }


   
/**
     * Delete polls of the user
     *
     * @return boolean True on success
     */
   
function delete_polls()
    {
        global
$DB, $current_User;

       
// If current user can moderate this user then it is allowed to delete all user data even if it wouldn't be allowed otherwise.
       
$current_user_can_moderate = $current_User->can_moderate_user( $this->ID );

       
$DB->begin();

       
$PollCache = & get_PollCache();
       
$PollCache->clear();
       
$PollCache->load_where( 'pqst_owner_user_ID = '.$this->ID );

       
$result = false;
        while( (
$iterator_Poll = & $PollCache->get_next() ) != NULL )
        {    
// Iterate through PollCache:
           
if( $current_user_can_moderate ||
               
check_user_perm( 'polls', 'edit', false, $iterator_Poll ) )
            {
// Current user has a permission to delete this poll
                // Delete the poll from DB:
               
$result = $iterator_Poll->dbdelete();
                if( !
$result )
                {
                    break;
                }
            }
        }

        if(
$result )
        {
           
$DB->commit();
        }
        else
        {
           
$DB->rollback();
        }

        return
$result;
    }


   
/**
     * Get number of posts and percent of published posts by this user
     *
     * @param array Params
     * @return string Result
     */
   
function get_reputation_posts( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'text' => T_( '%s (%s%% are public)' ),
            ),
$params );

       
$total_num_posts = $this->get_num_posts();
       
$public_num_posts = $this->get_num_posts( 'published' );
        if(
$total_num_posts > 0 )
        {
// Calc percent of published posts
           
$public_percent = floor( $public_num_posts / $total_num_posts * 100 );
        }
        else
        {
// To avoid devision by zero
           
$public_percent = 0;
        }

        if(
$total_num_posts > 0 )
        {    
// Make a link to page with user's posts:
           
global $current_User;
            if(
is_admin_page() && is_logged_in() &&
                (
$this->ID == $current_User->ID || check_user_perm( 'users', 'view' ) ) )
            {    
// For back-office
               
global $admin_url;
               
$total_num_posts_url = $admin_url.'?ctrl=user&amp;user_tab=activity&amp;user_ID='.$this->ID;
            }
            else
            {    
// For front-office
               
global $Collection, $Blog;
                if( ! empty(
$Blog ) )
                {    
// Only if blog is defined
                   
$total_num_posts_url = url_add_param( $Blog->gen_blogurl(), 'disp=useritems&amp;user_ID='.$this->ID );
                }
            }
        }

        if( empty(
$total_num_posts_url ) )
        {    
// No link to view posts
           
$total_num_posts = '<b>'.$total_num_posts.'</b>';
        }
        else
        {    
// Set a posts number as link if it is allowed:
           
$total_num_posts = '<a href="'.$total_num_posts_url.'"><b>'.$total_num_posts.'</b></a>';
        }

        return
sprintf( $params['text'], $total_num_posts, $public_percent );
    }


   
/**
     * Get number of comments and percent of published comments and number of helpful votes on comments by this user
     *
     * @param array Params
     * @return string Result
     */
   
function get_reputation_comments( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'view_type'     => 'simple', // 'simple', 'extended'
               
'text_simple'   => T_( '%s has posted %s comments (%s%% of which are public). %s of these comments have been found useful by %s different users.' ),
               
'text_extended' => T_( '%s has posted %s comments (%s%% of which are public).<br />%s voted useful by %s different users.<br />%s voted NOT useful by %s different users.<br />%s considered OK by %s different users.<br />%s considered SPAM by %s different users.' ),
            ),
$params );

       
$total_num_comments = $this->get_num_comments();
       
$public_num_comments = $this->get_num_comments( 'published' );
        if(
$total_num_comments > 0 )
        {
// Calc percent of published comments
           
$public_percent = floor( $public_num_comments / $total_num_comments * 100 );
        }
        else
        {
// To avoid devision by zero
           
$public_percent = 0;
        }

        if(
$total_num_comments > 0 )
        {
// Make a link to page with user's comments
           
$total_num_comments = '<a href="'.get_dispctrl_url( 'usercomments', 'user_ID='.$this->ID ).'"><b>'.$total_num_comments.'</b></a>';
        }
        else
        {
           
$total_num_comments = '<b>'.$total_num_comments.'</b>';
        }

       
// Get number of helpful votes on comments for this user
       
global $DB;
       
$comments_SQL = new SQL();
        if(
$params['view_type'] == 'simple' )
        {
// Simple view
           
$comments_SQL->SELECT( 'cmvt_user_ID AS user_ID, COUNT(*) AS cnt' );
        }
        else
        {
// Extended view
           
$comments_SQL->SELECT( 'cmvt_user_ID AS user_ID, cmvt_helpful, cmvt_spam' );
        }
       
$comments_SQL->FROM( 'T_comments' );
       
$comments_SQL->FROM_add( 'INNER JOIN T_comments__votes ON comment_ID = cmvt_cmt_ID' );
       
$comments_SQL->WHERE( 'comment_author_user_ID = '.$this->ID );
       
$comments_SQL->WHERE_and( 'comment_status IN ( "published", "community", "protected", "review" )' );
        if(
$params['view_type'] == 'simple' )
        {
// Simple view
           
$comments_SQL->WHERE_and( 'cmvt_helpful = 1' );
           
$comments_SQL->GROUP_BY( 'user_ID' );
        }

        if(
$params['view_type'] == 'simple' )
        {
// Simple view
           
$votes = $DB->get_assoc( $comments_SQL->get() );

           
// Calculate total votes from all users
           
$users_count_useful = count( $votes );
           
$votes_count_useful = 0;
            foreach(
$votes as $user_votes )
            {
               
$votes_count_useful += $user_votes;
            }

            return
sprintf( $params['text_simple'],
               
$this->get_username(), $total_num_comments, $public_percent,
               
'<b class="green">'.$votes_count_useful.'</b>', '<b>'.$users_count_useful.'</b>' );
        }
        else
        {
// Extended view
           
$votes = $DB->get_results( $comments_SQL->get() );

           
$votes_count_useful = 0;
           
$users_count_useful = array();
           
$votes_count_not = 0;
           
$users_count_not = array();
           
$votes_count_ok = 0;
           
$users_count_ok = array();
           
$votes_count_spam = 0;
           
$users_count_spam = array();
            foreach(
$votes as $vote )
            {
                if(
$vote->cmvt_helpful === '1' )
                {
// Useful votes
                   
$votes_count_useful++;
                   
$users_count_useful[ $vote->user_ID ] = NULL;
                }
                elseif(
$vote->cmvt_helpful === '-1' )
                {
// Not useful votes
                   
$votes_count_not++;
                   
$users_count_not[ $vote->user_ID ] = NULL;
                }
                if(
$vote->cmvt_spam === '-1' )
                {
// Ok votes
                   
$votes_count_ok++;
                   
$users_count_ok[ $vote->user_ID ] = NULL;
                }
                elseif(
$vote->cmvt_spam === '1' )
                {
// Spam votes
                   
$votes_count_spam++;
                   
$users_count_spam[ $vote->user_ID ] = NULL;
                }
            }

            return
sprintf( $params['text_extended'],
               
$this->get_username(), $total_num_comments, $public_percent,
               
'<b class="green">'.$votes_count_useful.'</b>', '<b>'.count( $users_count_useful ).'</b>',
               
'<b class="red">'.$votes_count_not.'</b>', '<b>'.count( $users_count_not ).'</b>',
               
'<b class="green">'.$votes_count_ok.'</b>', '<b>'.count( $users_count_ok ).'</b>',
               
'<b class="red">'.$votes_count_spam.'</b>', '<b>'.count( $users_count_spam ).'</b>' );
        }
    }


   
/**
     * Get number of files (and number of helpful votes on photos only) by this user
     *
     * @param array Params
     * @return string Result
     */
   
function get_reputation_files( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'view_type'           => 'simple', // 'simple', 'extended'
               
'file_type'           => 'image', // 'image', 'audio', 'other'
               
'text_image_simple'   => T_( '%s has uploaded %s photos. %s of these photos have been liked by %s different users.' ),
               
'text_image_extended' => T_( '%s has uploaded %s photos.<br />%s voted up (liked) by %s different users.<br />%s voted down by %s different users.<br />%s considered INAPPROPRIATE by %s different users.<br />%s considered SPAM by %s different users.' ),
               
'text_audio'          => T_( '%s has uploaded %s audio files.' ),
               
'text_video'          => T_( '%s has uploaded %s video files.' ),
               
'text_other'          => T_( '%s has uploaded %s other files.' ),
            ),
$params );

        switch(
$params['file_type'] )
        {
            case
'image':
               
// Number of photos
               
global $DB;
               
// Get number of helpful votes on links for this user
               
$links_SQL = new SQL();
                if(
$params['view_type'] == 'simple' )
                {
// Simple view
                   
$links_SQL->SELECT( 'lvot_user_ID AS user_ID, COUNT(*) AS cnt' );
                }
                else
                {
// Extended view
                   
$links_SQL->SELECT( 'lvot_user_ID AS user_ID, lvot_like, lvot_inappropriate, lvot_spam' );
                }
               
$links_SQL->FROM( 'T_links' );
               
$links_SQL->FROM_add( 'INNER JOIN T_files ON file_ID = link_file_ID' );
               
$links_SQL->FROM_add( 'INNER JOIN T_links__vote ON link_ID = lvot_link_ID' );
               
$links_SQL->WHERE( 'link_creator_user_ID = '.$this->ID );
               
$links_SQL->WHERE_and( 'file_type = '.$DB->quote( $params['file_type'] ) );
                if(
$params['view_type'] == 'simple' )
                {
// Simple view
                   
$links_SQL->WHERE_and( 'lvot_like = 1' );
                   
$links_SQL->GROUP_BY( 'user_ID' );
                }


                if(
$params['view_type'] == 'simple' )
                {
// Simple view
                   
$votes = $DB->get_assoc( $links_SQL->get() );

                   
// Calculate total votes from all users
                   
$users_count_up = count( $votes );
                   
$votes_count_up = 0;
                    foreach(
$votes as $user_votes )
                    {
                       
$votes_count_up += $user_votes;
                    }
                    return
sprintf( $params['text_image_simple'],
                       
$this->get_username(), '<b>'.$this->get_num_files( 'image' ).'</b>',
                       
'<b class="green">'.$votes_count_up.'</b>', '<b>'.$users_count_up.'</b>' );
                }
                else
                {
// Extended view
                   
$votes = $DB->get_results( $links_SQL->get() );

                   
$votes_count_up = 0;
                   
$users_count_up = array();
                   
$votes_count_down = 0;
                   
$users_count_down = array();
                   
$votes_count_inappropriate = 0;
                   
$users_count_inappropriate = array();
                   
$votes_count_spam = 0;
                   
$users_count_spam = array();
                    foreach(
$votes as $vote )
                    {
                        if(
$vote->lvot_like === '1' )
                        {
// Up votes
                           
$votes_count_up++;
                           
$users_count_up[ $vote->user_ID ] = NULL;
                        }
                        elseif(
$vote->lvot_like === '-1' )
                        {
// Down votes
                           
$votes_count_down++;
                           
$users_count_down[ $vote->user_ID ] = NULL;
                        }
                        if(
$vote->lvot_inappropriate === '1' )
                        {
// Inappropriate votes
                           
$votes_count_inappropriate++;
                           
$users_count_inappropriate[ $vote->user_ID ] = NULL;
                        }
                        if(
$vote->lvot_spam === '1' )
                        {
// Spam votes
                           
$votes_count_spam++;
                           
$users_count_spam[ $vote->user_ID ] = NULL;
                        }
                    }

                    return
sprintf( $params['text_image_extended'],
                       
$this->get_username(), '<b>'.$this->get_num_files( 'image' ).'</b>',
                       
'<b class="green">'.$votes_count_up.'</b>', '<b>'.count( $users_count_up ).'</b>',
                       
'<b class="red">'.$votes_count_down.'</b>', '<b>'.count( $users_count_down ).'</b>',
                       
'<b class="red">'.$votes_count_inappropriate.'</b>', '<b>'.count( $users_count_inappropriate ).'</b>',
                       
'<b class="red">'.$votes_count_spam.'</b>', '<b>'.count( $users_count_spam ).'</b>' );
                }
                break;

            case
'audio':
               
// Number of audio files
               
return sprintf( $params['text_audio'], $this->get_username(), '<b>'.$this->get_num_files( 'audio' ).'</b>' );

            case
'video':
               
// Number of video files
               
return sprintf( $params['text_video'], $this->get_username(), '<b>'.$this->get_num_files( 'video' ).'</b>' );

            case
'other':
               
// Number of other files
               
return sprintf( $params['text_other'], $this->get_username(), '<b>'.$this->get_num_files( 'other' ).'</b>' );
        }
    }


   
/**
     * Get total size of uploaded files
     *
     * @param array Params
     * @return string Result
     */
   
function get_reputation_total_upload( $params = array() )
    {
       
$params = array_merge( array(
               
'text' => T_('%s has uploaded a total of %s')
            ),
$params );

       
$total_upload = $this->get_total_upload();
        if( empty(
$total_upload ) )
        {
           
$total_upload = '0 '.T_('B.');
        }
        else
        {
           
$total_upload = bytesreadable( $total_upload );
        }

        return
sprintf( $params['text'], $this->get_username(), '<b>'.$total_upload.'</b>' );
    }


   
/**
     * Get number of spam votes which were made by this user
     *
     * @param array Params
     * @return string Result
     */
   
function get_reputation_spam( $params = array() )
    {
       
// Make sure we are not missing any param:
       
$params = array_merge( array(
               
'text' => T_( '%s successfully reported %s spams and/or spammers!' ),
            ),
$params );

        global
$DB, $UserSettings;

       
$comments_SQL = new SQL( 'Get number of spam votes on comments by this user' );
       
$comments_SQL->SELECT( 'COUNT(*) AS cnt' );
       
$comments_SQL->FROM( 'T_comments__votes' );
       
$comments_SQL->WHERE( 'cmvt_user_ID = '.$this->ID );
       
$comments_SQL->WHERE_and( 'cmvt_spam = 1' );

       
$links_SQL = new SQL( 'Get number of spam votes on links by this user' );
       
$links_SQL->SELECT( 'COUNT(*) AS cnt' );
       
$links_SQL->FROM( 'T_links__vote' );
       
$links_SQL->WHERE( 'lvot_user_ID = '.$this->ID );
       
$links_SQL->WHERE_and( 'lvot_spam = 1' );

       
$votes = $DB->get_var( 'SELECT SUM( cnt )
            FROM ('
.$comments_SQL->get().' UNION ALL '.$links_SQL->get().') AS tbl' );

       
// Get spam fighter score for the users that were reported and deleted
       
$votes += intval( $UserSettings->get( 'spam_fighter_score', $this->ID ) );

        return
sprintf( $params['text'], $this->get_username(), '<b>'.$votes.'</b>' );
    }


   
/**
     * Get status of user email
     *
     * @return string Status: 'unknown', 'working', 'unattended', 'redemption', 'warning', 'suspicious1', 'suspicious2', 'suspicious3', 'prmerror', 'spammer'
     */
   
function get_email_status()
    {
       
$user_email = $this->get( 'email' );

       
$EmailAddressCache = & get_EmailAddressCache();
        if( !empty(
$user_email ) && $EmailAddress = & $EmailAddressCache->get_by_name( $user_email, false, false ) )
        {
// The email of this user is located in the DB table
           
return $EmailAddress->get( 'status' );
        }
        else
        {
// There is no email address in the DB table
           
return 'unknown';
        }
    }


   
/**
     * Get domain of user email
     *
     * @return string Domain
     */
   
function get_email_domain()
    {
       
// Extract domain from email address:
       
return preg_replace( '#^[^@]+@#', '', $this->get( 'email' ) );
    }


   
/**
     * Check if user has a permission to moderate the user
     *
     * @param integer User ID
     * @return boolean TRUE on success
     */
   
function can_moderate_user( $user_ID, $assert = false )
    {
        if(
$this->check_perm( 'users', 'edit' ) )
        {
// User can edit all users
           
return true;
        }

        if(
$this->check_perm( 'users', 'moderate', $assert ) )
        {
// User can moderate other user but we should to compare levels of users groups
           
$UserCache = & get_UserCache();
            if(
$target_User = $UserCache->get_by_ID( $user_ID, false, false ) )
            {
                if(
$target_User->get_Group()->get( 'level' ) < $this->get_Group()->get( 'level' ) )
                {
// User can moderate only users with level lower than own level
                   
return true;
                }
            }
        }

        if(
$assert )
        {
// We can't let this go on!
           
debug_die( sprintf( T_('User #%s has no permission to edit user #%s!'), $this->ID, $user_ID ) );
        }

        return
false;
    }


   
/**
     * Send an email notification when user account has been changed
     *
     * @param boolean true if the main profile picture was changed, false otherwise
     * @param mixed false if there was no new profile picture upload, the newly uploaded file ID otherwise
     */
   
function send_account_changed_notification( $avatar_changed = false, $new_avatar_upload = false )
    {
        if( !
is_logged_in() )
        {
// User must be logged in for this action
           
return;
        }

        if( empty(
$this->significant_changed_values ) && ( ! $avatar_changed ) && ( ! $new_avatar_upload ) )
        {
// Nothing important was changed, so no need to send changed notification
           
return;
        }

       
$email_template_params = array(
               
'user_ID' => $this->ID,
               
'login'   => $this->login,
               
'fields'  => array(
                   
'login'     => array( 'title' => /* TRANS: noun */ NT_('Login') ),
                   
'grp_ID'  => array( 'title' => NT_('Group'), 'className' => 'Group' ),
                   
'nickname'  => array( 'title' => NT_('Nickname') ),
                   
'firstname' => array( 'title' => NT_('First name') ),
                   
'lastname'  => array( 'title' => NT_('Last name') ),
                   
'gender'    => array( 'title' => NT_('Gender') ),
                   
'ctry_ID'   => array( 'title' => NT_('Country'), 'className' => 'Country' ),
                   
'rgn_ID'    => array( 'title' => NT_('Region'), 'className' => 'Region' ),
                   
'subrg_ID'  => array( 'title' => NT_('Sub-region'), 'className' => 'Subregion' ),
                   
'city_ID'   => array( 'title' => NT_('City'), 'className' => 'City' ),
                ),
               
'avatar_changed'    => $avatar_changed,
               
'new_avatar_upload' => $new_avatar_upload,
            );

        foreach(
$email_template_params['fields'] as $user_field_name => $user_field_data )
        {
// Set new and previous values for email template
           
$field_was_changed = ( isset( $this->significant_changed_values[ $user_field_name ] ) && ( ! empty( $this->significant_changed_values[ $user_field_name ] ) ) );
            if( isset(
$user_field_data['className'] ) )
            {
// The field value is an object ID, get the object name
               
$Cache = call_user_func( 'get_'.$user_field_data['className'].'Cache' );
               
$Object = & $Cache->get_by_ID( $this->get( $user_field_name ), false, false );
               
$user_field_data['new'] = empty( $Object ) ? NULL : $Object->get_name();
                if(
$field_was_changed )
                {
// Get the old display value of the field
                   
$Object = & $Cache->get_by_ID( $this->significant_changed_values[ $user_field_name ] );
                   
$user_field_data['old'] = $Object->get_name();
                }
            }
            elseif(
$user_field_name == 'gender' )
            {
// This is the gender field, get the display name of the gender
               
$user_field_data['new'] = $this->get_gender();
                if(
$field_was_changed )
                {
                   
$user_field_data['old'] = ( $this->significant_changed_values[ $user_field_name ] == 'M' ) ? T_('A man') : T_('A woman');
                }
            }
            else
            {
// Get the field value
               
$user_field_data['new'] = $this->get( $user_field_name );
                if(
$field_was_changed )
                {
// The field was changed
                   
$user_field_data['old'] = $this->significant_changed_values[ $user_field_name ];
                }
            }

            if( !isset(
$user_field_data['old'] ) )
            {
// The field was not changed or the old value was empty
               
$user_field_data['old'] = array_key_exists( $user_field_name, $this->significant_changed_values ) ? NULL : $user_field_data['new'];
            }

           
$email_template_params['fields'][ $user_field_name ] = $user_field_data;
        }

       
send_admin_notification( NT_('User profile changed'), 'account_changed', $email_template_params );
       
// Clear changed values
       
$this->significant_changed_values = array();
    }


   
/**
     * Get organizations that user has selected
     *
     * @return array Organizations( key => org_ID, value => array( 'name', 'accepted', 'role' )
     */
   
function get_organizations_data()
    {
        if( ! isset(
$this->organizations ) )
        {    
// Get the organizations from DB
           
global $DB;
           
$SQL = new SQL();
           
$SQL->SELECT( 'org_ID, org_name, uorg_accepted, uorg_role, uorg_priority' );
           
$SQL->FROM( 'T_users__user_org' );
           
$SQL->FROM_add( 'INNER JOIN T_users__organization ON org_ID = uorg_org_ID' );
           
$SQL->WHERE( 'uorg_user_ID = '.$DB->quote( $this->ID ) );
           
$SQL->ORDER_BY( 'uorg_accepted DESC, uorg_org_ID' );
           
$organizations = $DB->get_results( $SQL->get() );
           
$this->organizations = array();
            foreach(
$organizations as $organization )
            {
               
$this->organizations[ $organization->org_ID ] = array(
                       
'name'     => $organization->org_name,
                       
'accepted' => $organization->uorg_accepted,
                       
'role'     => $organization->uorg_role,
                       
'priority' => $organization->uorg_priority,
                    );
            }
        }

        return
$this->organizations;
    }


   
/**
     * Get organizations that are accepted for this user
     *
     * @return array Organizations: array( 'name', 'url' )
     */
   
function get_organizations()
    {
        global
$DB;

       
$SQL = new SQL();
       
$SQL->SELECT( 'org_name AS name, org_url AS url' );
       
$SQL->FROM( 'T_users__user_org' );
       
$SQL->FROM_add( 'INNER JOIN T_users__organization ON org_ID = uorg_org_ID' );
       
$SQL->WHERE( 'uorg_user_ID = '.$DB->quote( $this->ID ) );
       
// Organization must be accepted by Admin
       
$SQL->WHERE_and( 'uorg_accepted = 1' );
       
$SQL->ORDER_BY( 'org_name' );

        return
$DB->get_results( $SQL->get() );
    }


   
/**
     * Update user's organizations in DB
     *
     * @param array Organization IDs
     * @param array Organization roles
     * @param array Organization priorities
     * @param boolean TRUE to auto accept user to organization (Used on install demo users)
     */
   
function update_organizations( $organization_IDs, $organization_roles = array(), $organization_priorities = array(), $force_accept = false )
    {
        global
$DB, $current_User, $Messages;

       
$OrganizationCache = & get_OrganizationCache();

       
$curr_orgs = $this->get_organizations_data();
       
$curr_org_IDs = array_keys( $curr_orgs );
       
$insert_orgs = array();
        foreach(
$organization_IDs as $o => $organization_ID )
        {
            if( ! (
$user_Organization = & $OrganizationCache->get_by_ID( $organization_ID, false, false ) ) )
            {
// Organization is not selected or doesn't exist in DB, Skip it
               
continue;
            }

           
// Check permission if current user can edit the organization:
           
$perm_edit_orgs = check_user_perm( 'orgs', 'edit', false, $user_Organization );
            if( !
$perm_edit_orgs && $user_Organization->get( 'accept' ) == 'no' )
            {    
// Skip this if current user cannot edit the organization and it has a setting to deny a member joining:
               
continue;
            }

            if( isset(
$insert_orgs[ $organization_ID ] ) )
            {    
// Don't join this user to same organization twice:
               
$Messages->add( sprintf( T_('You are already a member of "%s".'), $user_Organization->get_name() ), 'error' );
            }
            elseif(
in_array( $organization_ID, $curr_org_IDs ) )
            {
// User is already in this organization
               
if( $user_Organization->perm_role == 'owner and member' ||
                    (
is_logged_in() && $user_Organization->owner_user_ID == $current_User->ID ) ||
                    !
$curr_orgs[ $organization_ID ]['accepted'] )
                {    
// Update role if current user has permission or it is not accepted yet by admin
                   
$insert_orgs[ $organization_ID ]['role'] = ( empty( $organization_roles[ $o ] ) ? NULL : $organization_roles[ $o ] );
                }
                else
                {    
// Don't updated role by current user:
                   
$insert_orgs[ $organization_ID ]['role'] = $curr_orgs[ $organization_ID ]['role'];
                }

                if(
$perm_edit_orgs ||
                    (
is_logged_in() && $user_Organization->owner_user_ID == $current_User->ID ) ||
                    !
$curr_orgs[ $organization_ID ]['accepted'] )
                {    
// Update priority if current user has permission or it is not accepted yet by admin
                   
$insert_orgs[ $organization_ID ]['priority'] = ( empty( $organization_priorities[ $o ] ) ? NULL : $organization_priorities[ $o ] );
                }
                else
                {    
// Don't updated priority by current user
                   
$insert_orgs[ $organization_ID ]['priority'] = $curr_orgs[ $organization_ID ]['priority'];
                }
            }
            else
            {
// Insert user in new organization
               
$insert_orgs[ $organization_ID ]['role'] = ( empty( $organization_roles[ $o ] ) ? NULL : $organization_roles[ $o ] );
               
$insert_orgs[ $organization_ID ]['priority'] = ( empty( $organization_priorities[ $o ] ) ? NULL : $organization_priorities[ $o ] );
            }
        }

        if(
count( $insert_orgs ) > 0 )
        {
// Insert new records with user-org relations
           
$insert_org_SQL = 'REPLACE INTO T_users__user_org ( uorg_user_ID, uorg_org_ID, uorg_accepted, uorg_role, uorg_priority ) VALUES ';
           
$o = 0;
            foreach(
$insert_orgs as $insert_org_ID => $insert_org_data )
            {
                if( isset(
$curr_orgs[ $insert_org_ID ] ) )
                {
// If we are updating - Don't change the accept status
                   
$insert_orgs_accepted = $curr_orgs[ $insert_org_ID ]['accepted'];
                }
                else
                {
// If we are inserting - Set the accept status depends on user perms or func params
                   
$insert_orgs_accepted = '0';
                    if(
$force_accept )
                    {    
// Force the accept status for this request:
                       
$insert_orgs_accepted = '1';
                    }
                    else
                    {    
// Check if it can be autoaccepted:
                       
$user_Organization = & $OrganizationCache->get_by_ID( $insert_org_ID );
                        if(
$user_Organization->can_be_autoaccepted() )
                        {    
// This organization can be autoaccepted:
                           
$insert_orgs_accepted = '1';
                        }
                    }
                }
                if(
$o > 0 )
                {
// separator
                   
$insert_org_SQL .= ', ';
                }
               
$insert_org_SQL .= '( '.$this->ID.', '.$DB->quote( $insert_org_ID ).', '.$insert_orgs_accepted.', '.$DB->quote( $insert_org_data['role'] ).', '.$DB->quote( $insert_org_data['priority'] ).' )';
               
$o++;
            }
           
$DB->query( $insert_org_SQL );
        }

       
$delete_org_IDs = array_diff( $curr_org_IDs, array_keys( $insert_orgs ) );
        if(
count( $delete_org_IDs ) > 0 )
        {
// Delete old records that were replaced with new
           
$DB->query( 'DELETE FROM T_users__user_org
                WHERE uorg_user_ID = '
.$this->ID.'
                    AND uorg_org_ID IN ( '
.implode( ', ', $delete_org_IDs ).' )' );
        }
    }


   
/**
     * Update user's secondary groups in DB
     *
     * @param array Secondary group IDs
     */
   
function update_secondary_groups()
    {
        global
$DB, $current_User;

        if( !
is_logged_in() )
        {    
// User must be logged in for this action:
           
return;
        }

        if( !
$current_User->can_moderate_user( $this->ID ) )
        {    
// Current User has no permission to moderate this User:
           
return;
        }

        if( ! isset(
$this->old_secondary_groups ) || $this->old_secondary_groups === false )
        {    
// Skip a request without updating secondary groups:
           
return;
        }

       
// Check each old secondary group if it can be deleted by current user:
       
$delete_old_secondary_groups = array();
        foreach(
$this->old_secondary_groups as $old_secondary_Group )
        {
            if(
$old_secondary_Group->can_be_assigned() )
            {    
// Current user can delete only this group:
               
$delete_old_secondary_group_IDs[] = $old_secondary_Group->ID;
            }
        }
        if( ! empty(
$delete_old_secondary_group_IDs ) )
        {    
// Clear secondary groups only which can be touched by currrent user:
           
$DB->query( 'DELETE FROM T_users__secondary_user_groups
                WHERE sug_user_ID = '
.$this->ID.'
                    AND sug_grp_ID IN ( '
.$DB->quote( $delete_old_secondary_group_IDs ).' )' );
        }
       
// Set this var in order to don't update secondary group twice:
       
$this->old_secondary_groups = false;

        if( ! empty(
$this->secondary_groups ) )
        {    
// Update new secondary groups:
           
$new_secondary_grp_IDs = array();
            foreach(
$this->secondary_groups as $secondary_Group )
            {
                if(
$secondary_Group->can_be_assigned() )
                {    
// We can add this secondary group because current user has a permission:
                   
if( $secondary_Group->ID != $this->get( 'grp_ID' ) &&
                            !
in_array( $secondary_Group->ID, $new_secondary_grp_IDs ) )
                    {    
// Add except of primary user group and the duplicates:
                       
$new_secondary_grp_IDs[] = $secondary_Group->ID;
                    }
                }
            }

            if( ! empty(
$new_secondary_grp_IDs ) )
            {    
// Insert new secondary groups:
               
$DB->query( 'INSERT INTO T_users__secondary_user_groups ( sug_user_ID, sug_grp_ID )
                    VALUES ( '
.$this->ID.', '.implode( ' ), ( '.$this->ID.', ', $new_secondary_grp_IDs ).' )' );
            }
        }
    }


   
/**
     * Get secondary groups
     *
     * @return array Secondary groups array of Group objects
     */
   
function get_secondary_groups()
    {
        if( !
is_array( $this->secondary_groups ) )
        {    
// Initialize the secondary groups:
           
if( $this->ID > 0 )
            {    
// For existing user:
               
global $DB;

               
// Initialize SQL for secondary groups of this user:
               
$secondary_groups_SQL = new SQL( 'Get secondary groups of the User #'.$this->ID );
               
$secondary_groups_SQL->SELECT( '*' );
               
$secondary_groups_SQL->FROM( 'T_groups' );
               
$secondary_groups_SQL->FROM_add( 'INNER JOIN T_users__secondary_user_groups ON grp_ID = sug_grp_ID' );
               
$secondary_groups_SQL->WHERE( 'sug_user_ID = '.$this->ID );

               
// Load all secondary group objects of this user in cache:
               
$GroupCache = & get_GroupCache();
               
$GroupCache->clear();
               
$this->secondary_groups = $GroupCache->load_by_sql( $secondary_groups_SQL );
            }
            else
            {    
// For new creating user:
               
$this->secondary_groups = array();
            }
        }

        return
$this->secondary_groups;
    }


   
/**
     * Get primary & secondary user's groups
     *
     * @return array All user's groups array of Group objects
     */
   
function get_groups()
    {
       
$primary_group = $this->get_Group();
       
$secondary_groups = $this->get_secondary_groups();

       
// Prepend primary group to secondary groups:
       
array_unshift( $secondary_groups, $primary_group );

        return
$secondary_groups;
    }


   
/**
     * Get user name depending on general setting 'username_display'
     *
     * @param string Output format, see {@link format_to_output()}
     * @return string Login or preferred name
     */
   
function get_username( $format = NULL )
    {
        global
$Settings;

        if(
$Settings->get( 'username_display' ) == 'name' )
        {
// Get nickname or fullname
           
$username = $this->get_preferred_name();
        }
        else
        {
// Get login
           
$username = $this->get( 'login' );
        }

        if( !
is_null( $format ) )
        {
// Format output string
           
$username = format_to_output( $username, $format );
        }

        return
$username;
    }


   
/**
     * Calculate how much days user has after registration
     *
     * @return float
     */
   
function get_days_count_close()
    {
       
$date_start = strtotime( $this->datecreated );
       
$date_end = time();
       
$days = ( $date_end - $date_start ) / 86400;

        return
number_format( $days, 1, '.', '' );
    }


   
/**
     * Get count of own blogs of the user
     *
     * @return integer
     */
   
function get_own_blogs_count()
    {
        global
$DB;

       
$SQL = new SQL( 'Get a count of own collections of user #'.$this->ID );
       
$SQL->SELECT( 'COUNT( blog_ID )' );
       
$SQL->FROM( 'T_blogs' );
       
$SQL->WHERE( 'blog_owner_user_ID = '.$DB->quote( $this->ID ) );

        return
$DB->get_var( $SQL );
    }


   
/**
     * Check if user must accept terms & conditions
     *
     * @return boolean
     */
   
function must_accept_terms()
    {
        global
$UserSettings, $Settings;

        if( !
$Settings->get( 'site_terms_enabled' ) )
        {    
// The terms are not enabled:
           
return false;
        }

        if(
$UserSettings->get( 'terms_accepted', $this->ID ) )
        {    
// This user already accepted the terms:
           
return false;
        }

       
// Get ID of page with terms & conditions from global settings:
       
$terms_page_ID = intval( $Settings->get( 'site_terms' ) );

       
$ItemCache = & get_ItemCache();
        if(
$terms_page_ID && $terms_Item = & $ItemCache->get_by_ID( $terms_page_ID, false, false ) )
        {    
// The post for terms is defined and user still must accept it:
           
return true;
        }
        else
        {    
// No terms for this site:
           
return false;
        }
    }


   
/**
     * Get a count of flagged items by this user in current collection
     *
     * @param integer Collection ID, NULL to use current
     * @return integer
     */
   
function get_flagged_items_count( $coll_ID = NULL )
    {
        if( !
is_logged_in() )
        {    
// Only logged in users can have the flagged items:
           
return 0;
        }

        if(
$coll_ID === NULL )
        {    
// Use current Collection:
           
global $Collection, $Blog;
           
$flagged_Blog = $Blog;
        }
        else
        {    
// Use requested Collection:
           
$BlogCache = & get_BlogCache();
           
$flagged_Blog = & $BlogCache->get_by_ID( $coll_ID, false, false );
        }

        if( empty(
$flagged_Blog ) )
        {    
// Collection must be defined:
           
return 0;
        }

        if( ! isset(
$this->flagged_items_count ) )
        {    
// Initialize array once:
           
$this->flagged_items_count = array();
        }

        if( ! isset(
$this->flagged_items_count[ $coll_ID ] ) )
        {    
// Get it from DB only first time and then cache in var:
           
$flagged_ItemList2 = new ItemList2( $flagged_Blog, $flagged_Blog->get_timestamp_min(), $flagged_Blog->get_timestamp_max(), NULL, 'ItemCache', 'flagged' );

           
// Set additional debug info prefix for SQL queries in order to know what code executes it:
           
$flagged_ItemList2->query_title_prefix = 'Flagged Items';

           
// Filter only the flagged items:
           
$flagged_ItemList2->set_default_filters( array(
                   
'flagged' => 1
               
) );

           
// Run query initialization to get total rows:
           
$flagged_ItemList2->query_init();

           
$this->flagged_items_count[ $coll_ID ] = $flagged_ItemList2->total_rows;
        }

        return
$this->flagged_items_count[ $coll_ID ];
    }


   
/**
     * Get password driver
     *
     * @return object|FALSE Password driver OR FALSE if password driver is not detected for this user
     */
   
function get_PasswordDriver()
    {
        if(
$this->PasswordDriver === NULL )
        {    
// Initialize password driver:
           
$this->PasswordDriver = get_PasswordDriver( $this->pass_driver );
        }

        return
$this->PasswordDriver;
    }


   
/**
     * Get IDs List/Newsletters which are allowed for this User
     *
     * @return array Newsletter IDs
     */
   
function get_allowed_newsletter_IDs()
    {
        if( ! isset(
$this->allowed_newsletter_IDs ) )
        {    
// Load allowed newsletters from cache:
           
global $DB, $current_User;
           
$SQL = new SQL( 'Get allowed newsletters for User #'.$this->ID );
           
$SQL->SELECT( 'enlt_ID' );
           
$SQL->FROM( 'T_email__newsletter' );
           
$SQL->WHERE( 'enlt_active = 1' );
           
$perm_conditions = array( 'enlt_perm_subscribe = "anyone"' );
           
$check_groups = array();
            if(
check_user_perm( 'users', 'edit' ) )
            {    
// Allow to subscribe to forbidden newsletters by user admins:
               
$perm_conditions[] = 'enlt_perm_subscribe = "admin"';
               
$check_groups[] = $current_User->get( 'grp_ID' );
            }
           
$check_groups[] = $this->get( 'grp_ID' );
           
$check_groups = count( $check_groups ) > 1 ? '('.implode( '|', $check_groups ).')' : $check_groups[0];
           
$perm_conditions[] = 'enlt_perm_subscribe = "group" AND enlt_perm_groups REGEXP( "(^|,)'.$check_groups.'(,|$)" )';
           
$SQL->WHERE_and( implode( ' OR ', $perm_conditions ) );
           
$this->allowed_newsletter_IDs = $DB->get_col( $SQL );
        }

        return
$this->allowed_newsletter_IDs;
    }


   
/**
     * Get List/Newsletters which are allowed for this User
     *
     * @return array Newsletter objects
     */
   
function get_allowed_newsletters()
    {
        if( ! isset(
$this->allowed_newsletters ) )
        {    
// Load allowed newsletters from cache:
           
$newsletter_IDs = $this->get_allowed_newsletter_IDs();
           
$this->allowed_newsletters = array();
            if(
count( $newsletter_IDs ) > 0 )
            {
               
$NewsletterCache = & get_NewsletterCache();
               
$NewsletterCache->load_list( $newsletter_IDs );
                foreach(
$newsletter_IDs as $newsletter_ID )
                {
                    if(
$Newsletter = & $NewsletterCache->get_by_ID( $newsletter_ID, false, false ) )
                    {
                       
$this->allowed_newsletters[ $Newsletter->ID ] = $Newsletter;
                    }
                }
            }
        }

        return
$this->allowed_newsletters;
    }


   
/**
     * Check if the newsletter is allowed for this User
     *
     * @param integer Newsletter ID
     * @return boolean
     */
   
function is_allowed_newsletter( $newsletter_ID )
    {
        return
in_array( $newsletter_ID, $this->get_allowed_newsletter_IDs() );
    }


   
/**
     * Get IDs of newsletters which this user is subscribed on
     *
     * @param string Type: 'subscribed', 'unsubscribed', 'all'
     * @return array
     */
   
function get_newsletter_subscriptions( $type = 'subscribed' )
    {
        if( empty(
$this->ID ) )
        {    
// Only created user can has the newsletter subscriptions:
           
return array();
        }

        if( ! isset(
$this->newsletter_subscriptions ) )
        {    
// Initialize newsletter subscriptions array only on first request:
           
global $DB;

           
$this->newsletter_subscriptions = array(
                   
'subscribed'   => array(),
                   
'unsubscribed' => array(),
                );

           
$SQL = new SQL( 'Get list subscriptions of user #'.$this->ID );
           
$SQL->SELECT( 'enls_enlt_ID, enls_subscribed' );
           
$SQL->FROM( 'T_email__newsletter_subscription' );
           
$SQL->WHERE( 'enls_user_ID = '.$this->ID );
           
$newsletter_subscriptions = $DB->get_assoc( $SQL );
            foreach(
$newsletter_subscriptions as $newsletter_ID => $is_subscribed )
            {
                if(
$is_subscribed )
                {
                   
$this->newsletter_subscriptions['subscribed'][] = $newsletter_ID;
                }
                else
                {
                   
$this->newsletter_subscriptions['unsubscribed'][] = $newsletter_ID;
                }
            }
        }

        if(
$type == 'all' )
        {
            return
array_merge( $this->newsletter_subscriptions['subscribed'], $this->newsletter_subscriptions['unsubscribed'] );
        }
        else
        {
            return
$this->newsletter_subscriptions[ $type ];
        }
    }


   
/**
     * Check if this user is subscribed to requested newsletter
     *
     * @param integer Newsletter ID
     * @return boolean
     */
   
function is_subscribed( $newsletter_ID )
    {
        return
in_array( $newsletter_ID, $this->get_newsletter_subscriptions() );
    }


   
/**
     * Set newsletter subscriptions for this user
     *
     * @param array
     * @param array Optional subscription params. This will be applied to the next call to User::subscribe() and cleared thereafter.
     *              This will also be overridden by params passed to User::subscribe().
     *              - 'usertags': Comma-delimited user tags applied as part of subscription
     */
   
function set_newsletter_subscriptions( $new_subscriptions, $params = array() )
    {
       
$prev_subscriptions = $this->get_newsletter_subscriptions();

        if( !
is_array( $new_subscriptions ) )
        {    
// This must be an array:
           
$new_subscriptions = array();
        }

        if(
count( $new_subscriptions ) > 0 )
        {
            foreach(
$new_subscriptions as $n => $new_subscription_ID )
            {
               
// Format each value to integer:
               
$new_subscription_ID = intval( $new_subscription_ID );
                if( empty(
$new_subscription_ID ) ||
                    ( !
$this->is_allowed_newsletter( $new_subscription_ID ) &&
                      !
in_array( $new_subscription_ID, $prev_subscriptions ) ) )
                {    
// Unset wrong value or
                    //   if current User cannot subscribe this User to the new newsletter
                    //   but keep if it was subscribed before:
                   
unset( $new_subscriptions[ $n ] );
                }
                else
                {    
// Keep integer values only:
                   
$new_subscriptions[ $n ] = $new_subscription_ID;
                }
            }
        }

       
// Check if subscriptions were updated:
       
if( count( $prev_subscriptions ) != count( $new_subscriptions ) )
        {    
// If a count of subscriptions are not equals then mark they are updated:
           
$this->newsletter_subscriptions_updated = true;
        }
        else
        {
           
sort( $new_subscriptions );
           
sort( $prev_subscriptions );
            if(
$prev_subscriptions != $new_subscriptions )
            {    
// If subscriptions were really updated:
               
$this->newsletter_subscriptions_updated = true;
            }
        }

       
// Set new subscriptions:
       
$this->new_newsletter_subscriptions = $new_subscriptions;

       
// Set newsletter subscription params:
       
$this->newsletter_subscription_params = $params;
    }


   
/**
     * Subscribe to newsletter
     *
     * @param array|integer Newsletter IDs
     * @param array Optional subscription params
     *              - 'usertags': Comma-delimited user tags applied as part of subscription
     * @return integer|boolean # of rows affected or false if error
     */
   
function subscribe( $newsletter_IDs, $params = array() )
    {
        global
$DB, $localtimenow, $servertimenow, $current_User;

        if( empty(
$this->ID ) )
        {    
// Only created user can has the newsletter subscriptions:
           
return false;
        }

        if( empty(
$newsletter_IDs ) )
        {    
// No subscriptions:
           
return false;
        }

       
$DB->begin();

        if( !
is_array( $newsletter_IDs ) )
        {    
// Convert single newsletter ID to array:
           
$newsletter_IDs = array( $newsletter_IDs );
        }

       
// Decide what newsletter subscriptions should be inserted into DB and what should be updated:
       
$newsletter_subscriptions = $this->get_newsletter_subscriptions( 'all' );
       
$insert_newsletter_IDs = array_diff( $newsletter_IDs, $newsletter_subscriptions );
       
$update_newsletter_IDs = array_intersect( $newsletter_IDs, $newsletter_subscriptions );

       
// Make sure we get a list newletters IDs for resubscriptions:
       
$resubscribe_newsletter_IDs = array_intersect( $newsletter_IDs, $this->get_newsletter_subscriptions( 'unsubscribed' ) );

       
$r = 0;

        if(
count( $insert_newsletter_IDs ) )
        {    
// If new records should be inserted:
           
$insert_newsletter_sql_values = array();
            foreach(
$insert_newsletter_IDs as $insert_newsletter_ID )
            {
               
$insert_newsletter_sql_values[] = '( '.$DB->quote( $this->ID ).', '.$DB->quote( $insert_newsletter_ID ).', 1, '.$DB->quote( date2mysql( $localtimenow ) ).' )';
            }
           
$r += $DB->query( 'INSERT INTO T_email__newsletter_subscription ( enls_user_ID, enls_enlt_ID, enls_subscribed, enls_subscribed_ts )
                VALUES '
.implode( ', ', $insert_newsletter_sql_values ),
               
'Subscribe(insert new subscriptions) user #'.$this->ID.' to lists #'.implode( ',', $insert_newsletter_IDs ) );

           
// Send emails of campaigns which must be sent at subscription:
           
$this->send_auto_subscriptions( $insert_newsletter_IDs );
        }

        if(
count( $update_newsletter_IDs ) )
        {    
// If previous unsubscriptions should be subscribed again:
           
$r += $DB->query( 'UPDATE T_email__newsletter_subscription
            SET enls_subscribed = 1,
                enls_subscribed_ts = '
.$DB->quote( date2mysql( $localtimenow ) ).'
            WHERE enls_user_ID = '
.$DB->quote( $this->ID ).'
              AND enls_enlt_ID IN ( '
.$DB->quote( $update_newsletter_IDs ).' )
              AND enls_subscribed = 0'
,
           
'Subscribe(update unsubscriptions) user #'.$this->ID.' to lists #'.implode( ',', $update_newsletter_IDs ) );
        }

        if(
count( $insert_newsletter_IDs ) || count( $resubscribe_newsletter_IDs ) )
        {    
// Notify list owner of new subscriber:
           
$email_template_params = array_merge( array(
               
'subscribed_User'     => $this,
               
'subscribed_by_admin' => ( is_logged_in() && $current_User->login != $this->login ? $current_User->login : '' ),
            ), empty(
$this->newsletter_subscription_params ) ? array() : $this->newsletter_subscription_params, $params );

           
// Add to changes in subscription values:
           
$this->newsletter_subscription_changed_values[] = array( 'subscribe', array_unique( array_merge( $insert_newsletter_IDs, $resubscribe_newsletter_IDs ) ), $email_template_params );
        }

       
// Insert user states for newsletters with automations:
       
$first_step_SQL = new SQL( 'Get first step of automation' );
       
$first_step_SQL->SELECT( 'step_ID' );
       
$first_step_SQL->FROM( 'T_automation__step' );
       
$first_step_SQL->WHERE( 'step_autm_ID = aunl_autm_ID' );
       
$first_step_SQL->ORDER_BY( 'step_order ASC' );
       
$first_step_SQL->LIMIT( 1 );
       
$automations_SQL = new SQL( 'Get automations of the subscribed newsletters' );
       
$automations_SQL->SELECT( 'DISTINCT aunl_autm_ID, '.$this->ID.', ( '.$first_step_SQL->get().' ), '.$DB->quote( date2mysql( $servertimenow ) ) );
       
$automations_SQL->FROM( 'T_email__newsletter' );
       
$automations_SQL->FROM_add( 'INNER JOIN T_automation__newsletter ON enlt_ID = aunl_enlt_ID' );
       
$automations_SQL->FROM_add( 'LEFT JOIN T_automation__user_state ON aust_autm_ID = aunl_autm_ID AND aust_user_ID = '.$this->ID );
       
$automations_SQL->WHERE( 'enlt_ID IN ( '.$DB->quote( $newsletter_IDs ).' )' );
       
$automations_SQL->WHERE_and( 'aunl_autostart = 1' );
       
$automations_SQL->WHERE_and( 'aust_autm_ID IS NULL' );// Exclude already added automation user states
       
$DB->query( 'INSERT INTO T_automation__user_state ( aust_autm_ID, aust_user_ID, aust_next_step_ID, aust_next_exec_ts ) '.$automations_SQL->get(),
           
'Insert automation user states on subscribe to newsletters #'.implode( ',', $newsletter_IDs ).' for user #'.$this->ID );

       
$DB->commit();

       
// Unset flag in order not to run this twice:
       
$this->newsletter_subscriptions_updated = false;

       
// Unset newsletter subscription params for subsequent calls to this function:
       
unset( $this->newsletter_subscription_params );

       
// Update the CACHE array where we store who are subscribed already in order to avoid a duplicate entry error on second calling of this function:
       
$this->newsletter_subscriptions['subscribed'] = array_merge( $this->newsletter_subscriptions['subscribed'], $newsletter_IDs );
       
$this->newsletter_subscriptions['unsubscribed'] = array_diff( $this->newsletter_subscriptions['unsubscribed'], $newsletter_IDs );

        return
$r;
    }


   
/**
     * Unsubscribe from newsletter
     *
     * @param array|integer Newsletter IDs
     * @param array Optional subscription params
     *              - 'usertags': Comma-delimited user tags applied as part of unsubscription
     * @return integer|boolean # of rows affected or false if error
     */
   
function unsubscribe( $newsletter_IDs, $params = array() )
    {
        global
$DB, $localtimenow, $current_User;

        if( empty(
$this->ID ) )
        {    
// Only created user can has the newsletter subscriptions:
           
return false;
        }

        if( empty(
$newsletter_IDs ) )
        {    
// No subscriptions:
           
return false;
        }

        if( !
is_array( $newsletter_IDs ) )
        {    
// Convert single newsletter ID to array:
           
$newsletter_IDs = array( $newsletter_IDs );
        }

       
// Get list of newsletter IDs that will be updated:
       
$newsletter_subscriptions = $this->get_newsletter_subscriptions( 'subscribed' );
       
$update_newsletter_IDs = array_intersect( $newsletter_IDs, $newsletter_subscriptions );

       
$r = $DB->query( 'UPDATE T_email__newsletter_subscription
            SET enls_subscribed = 0,
                enls_unsubscribed_ts = '
.$DB->quote( date2mysql( $localtimenow ) ).'
            WHERE enls_user_ID = '
.$DB->quote( $this->ID ).'
              AND enls_enlt_ID IN ( '
.$DB->quote( $newsletter_IDs ).' )
              AND enls_subscribed = 1'
,
           
'Unsubscribe user #'.$this->ID.' from lists #'.implode( ',', $newsletter_IDs ) );

        if(
$r )
        {    
// If user has been unsubscribed from at least one newsletter,
            // Then this user must automatically exit all automations tied to those newsletters:
           
$DB->query( 'DELETE T_automation__user_state FROM T_automation__user_state
                INNER JOIN T_automation__newsletter ON aunl_autm_ID = aust_autm_ID
                WHERE aust_user_ID = '
.$DB->quote( $this->ID ).'
                  AND aunl_enlt_ID IN ( '
.$DB->quote( $newsletter_IDs ).' )
                  AND aunl_autoexit = 1'
,
               
'Exit user automatically from all automations tied to lists #'.implode( ',', $newsletter_IDs ) );
        }

        if(
count( $update_newsletter_IDs ) )
        {    
// Notify list owner of lost subscriber:
           
$email_template_params = array_merge( array(
               
'subscribed_User'       => $this,
               
'unsubscribed_by_admin' => ( is_logged_in() && $current_User->login != $this->login ? $current_User->login : '' ),
            ), empty(
$this->newsletter_unsubscription_params ) ? array() : $this->newsletter_unsubscription_params, $params );

           
// Add to changes in subscription values:
           
$this->newsletter_subscription_changed_values[] = array( 'unsubscribe', $update_newsletter_IDs, $email_template_params );
        }

       
// Unset newsletter unsubscription params for subsequent calls to this function:
       
unset( $this->newsletter_unsubscription_params );

       
// Update the CACHE array where we store who are subscribed already in order to avoid a duplicate entry error on second calling of this function:
       
$this->newsletter_subscriptions['subscribed'] = array_diff( $this->newsletter_subscriptions['subscribed'], $update_newsletter_IDs );
       
$this->newsletter_subscriptions['unsubscribed'] = array_unique( array_merge( $this->newsletter_subscriptions['unsubscribed'], $update_newsletter_IDs ) );

        return
$r;
    }


   
/**
     * Send emails of campaign which must be sent at subscription
     *
     * @param array Newsletters IDs
     */
   
function send_auto_subscriptions( $newsletter_IDs )
    {
        global
$DB;

       
// Additional check to know what newsletter is really new, in order to exclude the updating subscriptions:
       
$newsletter_subscriptions = $this->get_newsletter_subscriptions( 'all' );
       
$new_subscriptions = array_diff( $newsletter_IDs, $newsletter_subscriptions );

        if(
count( $new_subscriptions ) )
        {    
// User is really subscribing to new newsletters:
           
$EmailCampaignCache = & get_EmailCampaignCache();
           
$EmailCampaignCache->clear();
           
$EmailCampaignCache->load_where( 'ecmp_enlt_ID IN ( '.$DB->quote( $new_subscriptions ).' ) AND ecmp_welcome = 1' );

            foreach(
$EmailCampaignCache->cache as $EmailCampaign )
            {    
// Send a "Welcome" email of the campaign at subscription:
               
$r = $EmailCampaign->send_all_emails( false, array( $this->ID ), 'welcome' );
                if(
$r && $EmailCampaign->get( 'activate' ) )
                {    
// Set a flag to know email campaign which is used as "Activate" message has been sent now for this User:
                   
$this->welcome_activate_email_campaign_sent = true;
                }
            }
        }
    }


   
/**
     * Send email notification to owners of lists where user subscribed/unsubscribed
     *
     * @param string subscribed | unsubscribed | all
     */
   
function send_list_owner_notifications( $filter = 'all' )
    {
        if( ! empty(
$this->newsletter_subscription_changed_values ) )
        {
            foreach(
$this->newsletter_subscription_changed_values as $key => $value )
            {
                list(
$action, $newsletter_IDs, $email_template_params ) = $value;
                if(
$filter == 'all' || $filter == $action )
                {
                    switch(
$action )
                    {
                        case
'subscribe':
                           
$template_name = 'list_new_subscriber';
                            break;

                        case
'unsubscribe':
                           
$template_name = 'list_lost_subscriber';
                            break;

                        default:
                           
debug_die( 'Invalid type of subscription action' );
                    }

                   
// Send an email to the list owner:
                   
send_list_owner_notification( $newsletter_IDs, $template_name, $email_template_params );

                   
// Unset subscription change:
                   
unset( $this->newsletter_subscription_changed_values[$key] );
                }
            }
        }
    }


   
/**
     * Retrieves all tags assigned to user
     *
     * @return array of tags
     */
   
function get_usertags()
    {
        if( ! isset(
$this->user_tags ) )
        {
            global
$DB;

           
$tags_SQL = new SQL( 'Get tags' );
           
$tags_SQL->SELECT( 'utag_name' );
           
$tags_SQL->FROM( 'T_users__tag' );
           
$tags_SQL->FROM_add( 'INNER JOIN T_users__usertag ON uutg_emtag_ID = utag_ID' );
           
$tags_SQL->WHERE( 'uutg_user_ID = '.$DB->quote( $this->ID ) );

           
$this->user_tags = $DB->get_col( $tags_SQL );
        }

        return
$this->user_tags;
    }


   
/**
     * Get count of user's profile visitors
     *
     * @param boolean True to only count new unique profile visitors since the user viewed the list, False if total unique profile visitors
     * @return integer Visitor count
     */
   
function get_profile_visitors_count( $new_only = true )
    {
        global
$DB;

       
$count = $DB->get_var( 'SELECT '.( $new_only ? 'upvc_new_unique_visitors' : 'upvc_total_unique_visitors' ).' FROM T_users__profile_visit_counters WHERE upvc_user_ID = '.$this->ID );

        if(
is_null( $count ) )
        {
            return
0;
        }

        return
$count;
    }


   
/**
     * Save tags to DB
     *
     * @param string 'insert' | 'update'
     */
   
function insert_update_user_tags( $mode )
    {
        global
$DB;

        if( isset(
$this->user_tags ) )
        {
// Okay the tags are defined:

           
$DB->begin();

            if(
$mode == 'update' )
            {    
// delete previous tag associations:
                // Note: actual tags never get deleted
               
$DB->query( 'DELETE FROM T_users__usertag
                                            WHERE uutg_user_ID = '
.$this->ID, 'delete previous tags' );
            }

            if( ! empty(
$this->user_tags ) )
            {
               
// Find the tags that are already in the DB
               
$query = 'SELECT utag_name
                                        FROM T_users__tag
                                     WHERE utag_name IN ('
.$DB->quote( $this->user_tags ).')';
               
$existing_tags = $DB->get_col( $query, 0, 'Find existing tags' );

               
$new_tags = array_diff( $this->user_tags, $existing_tags );

                if( ! empty(
$new_tags ) )
                {
// insert new tags:
                   
$query = "INSERT INTO T_users__tag( utag_name ) VALUES ";
                    foreach(
$new_tags as $tag )
                    {
                       
$query .= '( '.$DB->quote( $tag ).' ),';
                    }
                   
$query = substr( $query, 0, strlen( $query ) - 1 );
                   
$DB->query( $query, 'insert new tags' );
                }

               
// ASSOC:
               
$query = 'INSERT INTO T_users__usertag( uutg_user_ID, uutg_emtag_ID )
                                  SELECT '
.$this->ID.', utag_ID
                                      FROM T_users__tag
                                     WHERE utag_name IN ('
.$DB->quote( $this->user_tags ).')';
               
$DB->query( $query, 'Make tag associations!' );
            }

           
$DB->commit();
        }
    }


   
/**
     * Split tags by comma or semicolon
     *
     * @param string The tags, separated by comma or semicolon
     */
   
function set_usertags_from_string( $user_tags )
    {
       
// Mark that tags has been updated, even if it is not sure because we do not want to execute extra db query
       
$this->dbchanges_flags['user_tags'] = true;

        if(
$user_tags === '' )
        {
           
$this->user_tags = array();
            return;
        }

       
$this->user_tags = preg_split( '/\s*[;,]+\s*/', $user_tags, -1, PREG_SPLIT_NO_EMPTY );
        foreach(
$this->user_tags as $t => $user_tag )
        {
            if(
substr( $user_tag, 0, 1 ) == '-' )
            {
// Prevent chars '-' in first position
               
$user_tag = preg_replace( '/^-+/', '', $user_tag );
            }
            if( empty(
$user_tag ) )
            {
// Don't save empty tag
               
unset( $this->user_tags[ $t ] );
            }
            else
            {
// Save the modifications for each tag
               
$this->user_tags[ $t ] = $user_tag;
            }
        }

       
// Remove the duplicate tags
       
$this->user_tags = array_unique( $this->user_tags );
    }


   
/**
     * Append user tags
     *
     * @param string/array comma separated tags or array of tags
     */
   
function add_usertags( $user_tags )
    {
        if( empty(
$user_tags ) )
        {
            return;
        }

        if( !
is_array( $user_tags ) )
        {
           
$user_tags = preg_split( '/\s*[;,]+\s*/', $user_tags, -1, PREG_SPLIT_NO_EMPTY );
        }

       
$this->get_usertags();
        foreach(
$user_tags as $t => $user_tag )
        {
           
// Replace ; and , in case $tags parameter was passed as an array
           
$user_tag = str_replace( array( ';', ',' ), ' ', $user_tag );

            if(
substr( $user_tag, 0, 1 ) == '-' )
            {
// Prevent chars '-' in first position
               
$user_tag = preg_replace( '/^-+/', '', $user_tag );
            }
            if( empty(
$user_tag ) )
            {
// Don't save empty tag
               
unset( $user_tags[ $t ] );
            }
            else
            {
// Append the tag and mark that tags has been updated
               
$this->dbchanges_flags['user_tags'] = true;
               
$this->user_tags[] = $user_tag;
            }
        }

       
// Remove the duplicate tags
       
$this->user_tags = array_unique( $this->user_tags );
    }


   
/**
     * Remove user tags
     *
     * @param string/array comma separated tags or array of tags
     */
   
function remove_usertags( $user_tags )
    {
        if( empty(
$user_tags ) )
        {
            return;
        }

        if( !
is_array( $user_tags ) )
        {
           
$user_tags = preg_split( '/\s*[;,]+\s*/', $user_tags, -1, PREG_SPLIT_NO_EMPTY );
        }

       
$this->get_usertags();

        foreach(
$user_tags as $t => $user_tag )
        {
            if( empty(
$user_tag ) )
            {    
// Skip empty tag:
               
continue;
            }
            elseif( (
$user_tag_index = array_search( $user_tag, $this->user_tags ) ) !== false )
            {    
// Remove user tag from array:
               
$this->dbchanges_flags['user_tags'] = true;
                unset(
$this->user_tags[ $user_tag_index ] );
            }
        }
    }
}

?>