Вход Регистрация
Файл: library/XenForo/Model/User.php
Строк: 2865
<?php

/**
 * Model for users.
 *
 * @package XenForo_Users
 */
class XenForo_Model_User extends XenForo_Model
{
    const 
FETCH_USER_PROFILE     0x01;
    const 
FETCH_USER_OPTION      0x02;
    const 
FETCH_USER_PRIVACY     0x04;
    const 
FETCH_USER_PERMISSIONS 0x08;
    const 
FETCH_LAST_ACTIVITY    0x10;

    
/**
     * Quick constant for fetching, profile, option, and privacy data.
     *
     * @var integer
     */
    
const FETCH_USER_FULL        0x07;

    
/**
     * Special value to use for a permanent ban
     *
     * @var integer
     */
    
const PERMANENT_BAN 0;

    public static 
$defaultGuestGroupId 1;
    public static 
$defaultRegisteredGroupId 2;
    public static 
$defaultAdminGroupId 3;
    public static 
$defaultModeratorGroupId 4;

    public static 
$guestPermissionCombinationId 1;

    
/**
     * Stores the unserialized value of xf_user.ignored for each user that has been inspected
     *
     * @var array [user id] => [ignored user id] => ignored user name
     */
    
protected $_ignoreCache = array();

    
/**
     * Simple way to update user data fields.
     *
     * @param integer|array $userId|$user
     * @param array|string Either the name of a single field, or an array of field-name => field-value pairs
     * @param mixed If the previous parameter is a string, use this as the field value
     *
     * @return XenForo_DataWriter_User
     */
    
public function update($user$field$value null)
    {
        
$userId $this->getUserIdFromUser($user);

        
$writer XenForo_DataWriter::create('XenForo_DataWriter_User');
        
$writer->setExistingData($userId);

        if (
$value === null)
        {
            if (
is_array($field))
            {
                
$writer->bulkSet($field);
            }
        }
        else if (
is_string($field))
        {
            
$writer->set($field$value);
        }

        
$writer->save();

        return 
$writer;
    }

    
/**
     * Fetches the user_id index from a user record
     *
     * @param integer|array $userId|$user
     *
     * @return integer User ID
     */
    
public static function getUserIdFromUser($user)
    {
        if (
is_scalar($user))
        {
            return 
$user;
        }

        if (
is_array($user) && isset($user['user_id']))
        {
            return 
$user['user_id'];
        }

        throw new 
XenForo_Exception('Unable to derive User ID from provided parameters.');
        return 
false;
    }

    
/**
     * Checks to see if the input string *might* be an email address - contains '@' after its first character
     *
     * @param String $email
     *
     * @return boolean
     */
    
public function couldBeEmail($email)
    {
        if (
strlen($email) < 1)
        {
            return 
false;
        }

        return (
strpos($email'@'1) !== false);
    }

    
/**
     * Gets all users. Can be restricted to valid users only with the
     * validOnly fetch option.
     *
     * @param array $fetchOptions User fetch options
     *
     * @return array Format: [user id] => user info
     */
    
public function getAllUsers(array $fetchOptions = array())
    {
        
$limitOptions $this->prepareLimitFetchOptions($fetchOptions);
        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');
        
$whereClause = (!empty($fetchOptions['validOnly']) ? 'WHERE user.user_state = 'valid' AND user.is_banned = 0' '');

        return 
$this->fetchAllKeyed($this->limitQueryResults(
            
'
                SELECT user.*
                    ' 
$joinOptions['selectFields'] . '
                FROM xf_user AS user
                ' 
$joinOptions['joinTables'] . '
                ' 
$whereClause '
                ' 
$orderClause '
            '
$limitOptions['limit'], $limitOptions['offset']
        ), 
'user_id');
    }

    
/**
     * Returns user records based on a list of usernames.
     *
     * @param array $usernames
     * @param array $fetchOptions User fetch options
     * @param array $invalidNames Returns a list of usernames that could not be found
     *
     * @return array Format: [user id] => info
     */
    
public function getUsersByNames(array $usernames, array $fetchOptions = array(), &$invalidNames = array())
    {
        
$usernames array_map('trim'$usernames);
        foreach (
$usernames AS $key => $username)
        {
            if (
$username === '')
            {
                unset(
$usernames[$key]);
            }
        }

        
$invalidNames = array();

        if (!
$usernames)
        {
            return array();
        }

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);
        
$validOnlyClause = (!empty($fetchOptions['validOnly']) ? 'AND user.user_state = 'valid' AND user.is_banned = 0' '');

        
$users $this->fetchAllKeyed('
            SELECT user.*
                ' 
$joinOptions['selectFields'] . '
            FROM xf_user AS user
            ' 
$joinOptions['joinTables'] . '
            WHERE user.username IN (' 
$this->_getDb()->quote($usernames) . ')
                ' 
$validOnlyClause '
        '
'user_id');

        if (
count($users) != count($usernames))
        {
            
$usernamesLower array_map('strtolower'$usernames);
            
$invalidNames $usernames;

            foreach (
$users AS $user)
            {
                do
                {
                    
$foundKey array_search(strtolower($user['username']), $usernamesLower);
                    if (
$foundKey !== false)
                    {
                        unset(
$invalidNames[$foundKey]);
                        unset(
$usernamesLower[$foundKey]);
                    }
                }
                while (
$foundKey !== false);
            }
        }

        return 
$users;
    }

    
/**
     * Get users with specified user IDs.
     *
     * @param array $userIds
     * @param array $fetchOptions
     *
     * @return array Format: [user id] => user info
     */
    
public function getUsersByIds(array $userIds, array $fetchOptions = array())
    {
        if (!
$userIds)
        {
            return array();
        }

        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed('
                SELECT user.*
                    ' 
$joinOptions['selectFields'] . '
                FROM xf_user AS user
                ' 
$joinOptions['joinTables'] . '
                WHERE user.user_id IN (' 
$this->_getDb()->quote($userIds) . ')
                ' 
$orderClause '
        '
'user_id');
    }

    
/**
     * Return all users logged from a particular IP address
     *
     * @param string $ip
     * @param array $fetchOptions
     *
     * @return array Format: [user id] => user info
     */
    
public function getUsersByIp($ip, array $fetchOptions = array())
    {
        if (!
$ip)
        {
            return array();
        }

        
$ip XenForo_Helper_Ip::convertIpStringToBinary($ip);
        if (!
$ip)
        {
            return array();
        }

        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed('
            SELECT user.*, ip.ip, MAX(ip.log_date) AS log_date
                ' 
$joinOptions['selectFields'] . '
                FROM xf_ip AS ip
                INNER JOIN xf_user AS user ON
                    (user.user_id = ip.user_id)
                ' 
$joinOptions['joinTables'] . '
                WHERE ip.ip = ?
                GROUP BY ip.user_id
                ' 
$orderClause '
        '
'user_id'$ip);
    }

    
/**
     * Return all users logged from a range of binary IPs
     *
     * @param string $lowerBound Binary IP range start
     * @param string $upperBound Binary IP range end
     * @param array $fetchOptions
     *
     * @return array Format: [user id] => user info
     */
    
public function getUsersByIpRange($lowerBound$upperBound, array $fetchOptions = array())
    {
        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed('
            SELECT user.*, ip.ip, MAX(ip.log_date) AS log_date
                ' 
$joinOptions['selectFields'] . '
                FROM xf_ip AS ip
                INNER JOIN xf_user AS user ON
                    (user.user_id = ip.user_id)
                ' 
$joinOptions['joinTables'] . '
                WHERE ip.ip >= ? AND ip.ip <= ? AND LENGTH(ip.ip) = ?
                GROUP BY ip.user_id
                ' 
$orderClause '
        '
'user_id', array($lowerBound$upperBoundstrlen($lowerBound)));
    }

    
/**
     * Gets users that match the specified conditions.
     *
     * @param array $conditions
     * @param array $fetchOptions
     *
     * @return array Format: [user id] => user info
     */
    
public function getUsers(array $conditions, array $fetchOptions = array())
    {
        
$whereClause $this->prepareUserConditions($conditions$fetchOptions);

        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');
        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);
        
$limitOptions $this->prepareLimitFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed($this->limitQueryResults(
            
'
                SELECT user.*
                    ' 
$joinOptions['selectFields'] . '
                FROM xf_user AS user
                ' 
$joinOptions['joinTables'] . '
                WHERE ' 
$whereClause '
                ' 
$orderClause '
            '
$limitOptions['limit'], $limitOptions['offset']
        ), 
'user_id');
    }

    
/**
     * Gets the user Ids that match the specified conditions.
     *
     * @param array $conditions
     * @param integer $start
     * @param integer $limit
     *
     * @return array
     */
    
public function getUserIds(array $conditions$start 0$limit 0)
    {
        
$fetchOptions = array();
        
$whereClause $this->prepareUserConditions($conditions$fetchOptions);

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->_getDb()->fetchCol($this->limitQueryResults('
            SELECT user.user_id
            FROM xf_user AS user
            ' 
$joinOptions['joinTables'] . '
            WHERE ' 
$whereClause '
                AND user.user_id > ' 
intval($start) . '
            ORDER BY user.user_id
        '
$limit));
    }

    
/**
     * Gets the count of users that match the specified conditions.
     *
     * @param array $conditions
     *
     * @return integer
     */
    
public function countUsers(array $conditions)
    {
        
$fetchOptions = array();
        
$whereClause $this->prepareUserConditions($conditions$fetchOptions);

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->_getDb()->fetchOne('
            SELECT COUNT(*)
            FROM xf_user AS user
            ' 
$joinOptions['joinTables'] . '
            WHERE ' 
$whereClause
        
);
    }

    public function 
getBirthdayUsers($month$day, array $conditions = array(), array $fetchOptions = array(), $publicOnly true)
    {
        
$this->addFetchOptionJoin($fetchOptionsself::FETCH_USER_OPTION);
        
$this->addFetchOptionJoin($fetchOptionsself::FETCH_USER_PROFILE);

        
$whereClause $this->prepareUserConditions($conditions$fetchOptions);

        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');
        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);
        
$limitOptions $this->prepareLimitFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed($this->limitQueryResults(
            
'
                SELECT user.*
                    ' 
$joinOptions['selectFields'] . '
                FROM xf_user AS user
                ' 
$joinOptions['joinTables'] . '
                WHERE user_profile.dob_month = ?
                    AND user_profile.dob_day = ?
                    ' 
. ($publicOnly ' AND user_option.show_dob_date = 1' '') . '
                    AND (' 
$whereClause ')
                ' 
$orderClause '
            '
$limitOptions['limit'], $limitOptions['offset']
        ), 
'user_id', array($month$day));
    }

    
/**
     * Gets the specified user by ID.
     *
     * @param integer $userId
     * @param array $fetchOptions User fetch options
     *
     * @return array|false
     */
    
public function getUserById($userId, array $fetchOptions = array())
    {
        if (empty(
$userId))
        {
            return 
false;
        }

        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->_getDb()->fetchRow('
            SELECT user.*
                ' 
$joinOptions['selectFields'] . '
            FROM xf_user AS user
            ' 
$joinOptions['joinTables'] . '
            WHERE user.user_id = ?
        '
$userId);
    }

    
/**
     * Returns a user record based on an input username
     *
     * @param string $username
     * @param array $fetchOptions User fetch options
     *
     * @return array|false
     */
    
public function getUserByName($username, array $fetchOptions = array())
    {
        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->_getDb()->fetchRow('
            SELECT user.*
                ' 
$joinOptions['selectFields'] . '
            FROM xf_user AS user
            ' 
$joinOptions['joinTables'] . '
            WHERE user.username = ?
        '
$username);
    }

    
/**
     * Returns a user record based on an input email
     *
     * @param string $email
     * @param array $fetchOptions User fetch options
     *
     * @return array|false
     */
    
public function getUserByEmail($email, array $fetchOptions = array())
    {
        
$joinOptions $this->prepareUserFetchOptions($fetchOptions);

        return 
$this->_getDb()->fetchRow('
            SELECT user.*
                ' 
$joinOptions['selectFields'] . '
            FROM xf_user AS user
            ' 
$joinOptions['joinTables'] . '
            WHERE user.email = ?
        '
$email);
    }

    
/**
     * Returns a user record based on an input username OR email
     *
     * @param string $input
     * @param array $fetchOptions User fetch options
     *
     * @return array|false
     */
    
public function getUserByNameOrEmail($input, array $fetchOptions = array())
    {
        if (
$this->couldBeEmail($input))
        {
            if (
$user $this->getUserByEmail($input$fetchOptions))
            {
                return 
$user;
            }
        }

        return 
$this->getUserByName($input$fetchOptions);
    }

    
/**
     * Prepares join-related fetch options.
     *
     * @param array $fetchOptions
     *
     * @return array Containing 'selectFields' and 'joinTables' keys.
     */
    
public function prepareUserFetchOptions(array $fetchOptions)
    {
        
$selectFields '';
        
$joinTables '';

        if (!empty(
$fetchOptions['join']))
        {
            if (
$fetchOptions['join'] & self::FETCH_USER_PROFILE)
            {
                
$selectFields .= ',
                    user_profile.*'
;
                
$joinTables .= '
                    LEFT JOIN xf_user_profile AS user_profile ON
                        (user_profile.user_id = user.user_id)'
;
            }

            
// TODO: optimise the join on user_option with serialization to user or user_profile
            
if ($fetchOptions['join'] & self::FETCH_USER_OPTION)
            {
                
$selectFields .= ',
                    user_option.*'
;
                
$joinTables .= '
                    LEFT JOIN xf_user_option AS user_option ON
                        (user_option.user_id = user.user_id)'
;
            }

            if (
$fetchOptions['join'] & self::FETCH_USER_PRIVACY)
            {
                
$selectFields .= ',
                    user_privacy.*'
;
                
$joinTables .= '
                    LEFT JOIN xf_user_privacy AS user_privacy ON
                        (user_privacy.user_id = user.user_id)'
;
            }

            if (
$fetchOptions['join'] & self::FETCH_USER_PERMISSIONS)
            {
                
$selectFields .= ',
                    permission_combination.cache_value AS global_permission_cache'
;
                
$joinTables .= '
                    LEFT JOIN xf_permission_combination AS permission_combination ON
                        (permission_combination.permission_combination_id = user.permission_combination_id)'
;
            }

            if (
$fetchOptions['join'] & self::FETCH_LAST_ACTIVITY)
            {
                
$selectFields .= ',
                    IF (session_activity.view_date IS NULL, user.last_activity, session_activity.view_date) AS effective_last_activity,
                    session_activity.view_date, session_activity.controller_name, session_activity.controller_action, session_activity.params, session_activity.ip'
;
                
$joinTables .= '
                    LEFT JOIN xf_session_activity AS session_activity ON
                        (session_activity.user_id = user.user_id AND session_activity.unique_key = user.user_id)'
;
            }
        }

        if (isset(
$fetchOptions['followingUserId']))
        {
            
$fetchOptions['followingUserId'] = intval($fetchOptions['followingUserId']);
            if (
$fetchOptions['followingUserId'])
            {
                
// note: quoting is skipped; intval'd above
                
$selectFields .= ',
                    IF(user_follow.user_id IS NOT NULL, 1, 0) AS following_' 
$fetchOptions['followingUserId'];
                
$joinTables .= '
                    LEFT JOIN xf_user_follow AS user_follow ON
                        (user_follow.user_id = user.user_id AND user_follow.follow_user_id = ' 
$fetchOptions['followingUserId'] . ')';
            }
            else
            {
                
$selectFields .= ',
                    0 AS following_0'
;
            }
        }

        if (isset(
$fetchOptions['nodeIdPermissions']))
        {
            
$fetchOptions['nodeIdPermissions'] = intval($fetchOptions['nodeIdPermissions']);
            
$selectFields .= ',
                permission.cache_value AS node_permission_cache'
;
            
$joinTables .= '
                LEFT JOIN xf_permission_cache_content AS permission
                    ON (permission.permission_combination_id = user.permission_combination_id
                        AND permission.content_type = '
node'
                        AND permission.content_id = ' 
$fetchOptions['nodeIdPermissions'] .')';
        }

        if (!empty(
$fetchOptions['customFields']) && is_array($fetchOptions['customFields']))
        {
            foreach (
$fetchOptions['customFields'] AS $customFieldId => $value)
            {
                if (
$value === '' || (is_array($value) && !$value))
                {
                    continue;
                }

                
$isExact = !empty($fetchOptions['customFieldsExact'][$customFieldId]);
                
$customFieldId preg_replace('/[^a-z0-9_]/i'''$customFieldId);
                
$selectFields .= ", user_field_value_$customFieldId.field_value AS custom_field_$customFieldId";

                if (
$value === true)
                {
                    
$joinTables .= "
                        LEFT JOIN xf_user_field_value AS user_field_value_
$customFieldId ON
                            (user_field_value_
$customFieldId.user_id = user.user_id
                            AND user_field_value_
$customFieldId.field_id = " $this->_getDb()->quote($customFieldId) . ")";
                }
                else
                {
                    
$possibleValues = array();
                    foreach ((array)
$value AS $possible)
                    {
                        if (
$isExact)
                        {
                            
$possibleValues[] = "user_field_value_$customFieldId.field_value = " $this->_getDb()->quote($possible);
                        }
                        else
                        {
                            
$possibleValues[] = "user_field_value_$customFieldId.field_value LIKE " XenForo_Db::quoteLike($possible'lr');
                        }
                    }

                    
$joinTables .= "
                        INNER JOIN xf_user_field_value AS user_field_value_
$customFieldId ON
                            (user_field_value_
$customFieldId.user_id = user.user_id
                            AND user_field_value_
$customFieldId.field_id = " $this->_getDb()->quote($customFieldId) . "
                            AND (" 
implode(' OR '$possibleValues) . "))";
                }
            }
        }

        return array(
            
'selectFields' => $selectFields,
            
'joinTables'   => $joinTables
        
);
    }

    
/**
     * Prepares a set of conditions to select users against.
     *
     * @param array $conditions List of conditions. (TODO: make list)
     * @param array $fetchOptions The fetch options that have been provided. May be edited if criteria requires.
     *
     * @return string Criteria as SQL for where clause
     */
    
public function prepareUserConditions(array $conditions, array &$fetchOptions)
    {
        
$db $this->_getDb();
        
$sqlConditions = array();

        if (!empty(
$conditions['user_id']))
        {
            if (
is_array($conditions['user_id']))
            {
                
$sqlConditions[] = 'user.user_id IN(' $db->quote($conditions['user_id']) . ')';
            }
            else
            {
                
$sqlConditions[] = 'user.user_id = ' $db->quote($conditions['user_id']);
            }
        }

        if (!empty(
$conditions['username']))
        {
            if (
is_array($conditions['username']))
            {
                
$sqlConditions[] = 'user.username LIKE ' XenForo_Db::quoteLike($conditions['username'][0], $conditions['username'][1], $db);
            }
            else
            {
                
$sqlConditions[] = 'user.username LIKE ' XenForo_Db::quoteLike($conditions['username'], 'lr'$db);
            }
        }

        
// this is mainly for dynamically filtering a search that already matches user names
        
if (!empty($conditions['username2']))
        {
            if (
is_array($conditions['username2']))
            {
                
$sqlConditions[] = 'user.username LIKE ' XenForo_Db::quoteLike($conditions['username2'][0], $conditions['username2'][1], $db);
            }
            else
            {
                
$sqlConditions[] = 'user.username LIKE ' XenForo_Db::quoteLike($conditions['username2'], 'lr'$db);
            }
        }

        if (!empty(
$conditions['usernames']) && is_array($conditions['usernames']))
        {
            
$sqlConditions[] = 'user.username IN (' $db->quote($conditions['usernames']) . ')';
        }

        if (!empty(
$conditions['email']))
        {
            if (
is_array($conditions['email']))
            {
                
$sqlConditions[] = 'user.email LIKE ' XenForo_Db::quoteLike($conditions['email'][0], $conditions['email'][1], $db);
            }
            else
            {
                
$sqlConditions[] = 'user.email LIKE ' XenForo_Db::quoteLike($conditions['email'], 'lr'$db);
            }
        }
        if (!empty(
$conditions['emails']) && is_array($conditions['emails']))
        {
            
$sqlConditions[] = 'user.email IN (' $db->quote($conditions['emails']) . ')';
        }

        if (!empty(
$conditions['user_group_id']))
        {
            if (
is_array($conditions['user_group_id']))
            {
                
$sqlConditions[] = 'user.user_group_id IN (' $db->quote($conditions['user_group_id']) . ')';
            }
            else
            {
                
$sqlConditions[] = 'user.user_group_id = ' $db->quote($conditions['user_group_id']);
            }
        }

        if (isset(
$conditions['gender']))
        {
            if (
is_array($conditions['gender']))
            {
                if (
$conditions['gender'])
                {
                    
$sqlConditions[] = 'user.gender IN (' $db->quote($conditions['gender']) . ')';
                }
            }
            else
            {
                
$sqlConditions[] = 'user.gender = ' $db->quote($conditions['gender']);
            }
        }

        if (!empty(
$conditions['secondary_group_ids']))
        {
            if (
is_array($conditions['secondary_group_ids']))
            {
                
$groupConds = array();
                foreach (
$conditions['secondary_group_ids'] AS $groupId)
                {
                    
$groupConds[] = 'FIND_IN_SET(' $db->quote($groupId) . ', user.secondary_group_ids)';
                }
                
$sqlConditions[] = '(' implode(' OR '$groupConds) . ')';
            }
            else
            {
                
$sqlConditions[] = 'FIND_IN_SET(' $db->quote($conditions['secondary_group_ids']) . ', user.secondary_group_ids)';
            }
        }

        if (!empty(
$conditions['not_secondary_group_ids']))
        {
            if (
is_array($conditions['not_secondary_group_ids']))
            {
                
$groupConds = array();
                foreach (
$conditions['not_secondary_group_ids'] AS $groupId)
                {
                    
$groupConds[] = 'FIND_IN_SET(' $db->quote($groupId) . ', user.secondary_group_ids) = 0';
                }
                
$sqlConditions[] = '(' implode(' AND '$groupConds) . ')';
            }
            else
            {
                
$sqlConditions[] = 'FIND_IN_SET(' $db->quote($conditions['not_secondary_group_ids']) . ', user.secondary_group_ids) = 0';
            }
        }

        if (isset(
$conditions['no_secondary_group_ids']))
        {
            if (
$conditions['no_secondary_group_ids'])
            {
                
$sqlConditions[] = "user.secondary_group_ids = ''";
            }
            else
            {
                
$sqlConditions[] = "user.secondary_group_ids <> ''";
            }
        }

        if (!empty(
$conditions['last_activity']) && is_array($conditions['last_activity']))
        {
            
$sqlConditions[] = $this->getCutOffCondition("user.last_activity"$conditions['last_activity']);
        }

        if (!empty(
$conditions['register_date']) && is_array($conditions['register_date']))
        {
            
$sqlConditions[] = $this->getCutOffCondition("user.register_date"$conditions['register_date']);
        }

        if (!empty(
$conditions['message_count']) && is_array($conditions['message_count']))
        {
            
$sqlConditions[] = $this->getCutOffCondition("user.message_count"$conditions['message_count']);
        }

        if (!empty(
$conditions['trophy_points']) && is_array($conditions['trophy_points']))
        {
            
$sqlConditions[] = $this->getCutOffCondition("user.trophy_points"$conditions['trophy_points']);
        }

        if (!empty(
$conditions['user_state']) && $conditions['user_state'] !== 'any')
        {
            if (
is_array($conditions['user_state']))
            {
                
$sqlConditions[] = 'user.user_state IN (' $db->quote($conditions['user_state']) . ')';
            }
            else
            {
                
$sqlConditions[] = 'user.user_state = ' $db->quote($conditions['user_state']);
            }
        }

        if (isset(
$conditions['is_admin']))
        {
            
$sqlConditions[] = 'user.is_admin = ' . ($conditions['is_admin'] ? 0);
        }

        if (isset(
$conditions['is_moderator']))
        {
            
$sqlConditions[] = 'user.is_moderator = ' . ($conditions['is_moderator'] ? 0);
        }

        if (isset(
$conditions['is_banned']))
        {
            
$sqlConditions[] = 'user.is_banned = ' . ($conditions['is_banned'] ? 0);
        }

        if (isset(
$conditions['is_staff']))
        {
            
$sqlConditions[] = 'user.is_staff = ' . ($conditions['is_staff'] ? 0);
        }

        if (!empty(
$conditions['receive_admin_email']))
        {
            
$sqlConditions[] = 'user_option.receive_admin_email = 1';
            
$this->addFetchOptionJoin($fetchOptionsself::FETCH_USER_OPTION);
        }

        if (!empty(
$conditions['adminQuickSearch']))
        {
            
$quotedString XenForo_Db::quoteLike($conditions['adminQuickSearch'], 'lr'$db);

            
$sqlConditions[] = 'user.username LIKE ' $quotedString ' OR user.email LIKE ' $quotedString;
        }

        
// these are conditions, but implemented via fetch options as they need a bunch of joins
        
if (!empty($conditions['customFields']) && empty($fetchOptions['customFields']))
        {
            
$fetchOptions['customFields'] = $conditions['customFields'];
        }
        if (!empty(
$conditions['customFieldsExact']) && empty($fetchOptions['customFieldsExact']))
        {
            
$fetchOptions['customFieldsExact'] = $conditions['customFieldsExact'];
        }

        return 
$this->getConditionsForClause($sqlConditions);
    }

    
/**
     * Construct 'ORDER BY' clause
     *
     * @param array $fetchOptions (uses 'order' key)
     * @param string $defaultOrderSql Default order SQL
     *
     * @return string
     */
    
public function prepareUserOrderOptions(array &$fetchOptions$defaultOrderSql '')
    {
        
$choices = array(
            
'username' => 'user.username',
            
'register_date' => 'user.register_date',
            
'message_count' => 'user.message_count',
            
'trophy_points' => 'user.trophy_points',
            
'like_count' => 'user.like_count',
            
'last_activity' => 'user.last_activity'
        
);
        return 
$this->getOrderByClause($choices$fetchOptions$defaultOrderSql);
    }

    
/**
     * Returns a full user record based on an input user ID. Equivalent to
     * calling getUserById including the FETCH_USER_FULL constanct
     *
     * @param integer $userId
     * @param array $fetchOptions User fetch options
     *
     * @return array|false
     */
    
public function getFullUserById($userId, array $fetchOptions = array())
    {
        if (!empty(
$fetchOptions['join']))
        {
            
$fetchOptions['join'] |= self::FETCH_USER_FULL;
        }
        else
        {
            
$fetchOptions['join'] = self::FETCH_USER_FULL;
        }

        return 
$this->getUserById($userId$fetchOptions);
    }

    
/**
     * Gets the visiting user's information based on their user ID.
     *
     * @param integer $userId
     *
     * @return array
     */
    
public function getVisitingUserById($userId)
    {
        
$userinfo $this->getUserById($userId, array(
            
'join' => self::FETCH_USER_FULL self::FETCH_USER_PERMISSIONS
        
));
        if (!
$userinfo)
        {
            return 
false;
        }

        
$userinfo['csrf_token_page'] = $userinfo['user_id'] . ',' XenForo_Application::$time
            
',' sha1(XenForo_Application::$time $userinfo['csrf_token']);

        if (
$userinfo['user_state'] != 'valid')
        {
            
// user is not valid yet, give them guest permissions
            
$userinfo $this->setPermissionsOnVisitorArray($userinfo);
        }

        return 
$userinfo;
    }

    
/**
     * Get the visiting user information for a guest.
     *
     * @return array
     */
    
public function getVisitingGuestUser()
    {
        
$options XenForo_Application::get('options');

        
$userinfo = array(
            
// xf_user
            
'user_id' => 0,
            
'username' => '',
            
'email' => '',
            
'gender' => '',
            
'language_id' => 0,
            
'style_id' => 0,
            
'timezone' => $options->guestTimeZone,
            
'visible' => 1,
            
'user_group_id' => self::$defaultGuestGroupId,
            
'secondary_group_ids' => '',
            
'display_style_group_id' => self::$defaultGuestGroupId,
            
'permission_combination_id' => 0,
            
'message_count' => 0,
            
'conversations_unread' => 0,
            
'register_date' => 0,
            
'last_activity' => 0,
            
'trophy_points' => 0,
            
'alerts_unread' => 0,
            
'avatar_date' => 0,
            
'avatar_width' => 0,
            
'avatar_height' => 0,
            
'gravatar' => '',
            
'user_state' => 'valid',
            
'is_moderator' => 0,
            
'is_admin' => 0,
            
'is_staff' => 0,
            
'is_banned' => 0,
            
'like_count' => 0,
            
'custom_title' => '',
            
'warning_points' => '',

            
// xf_user_profile
            
'dob_day' => 0,
            
'dob_month' => 0,
            
'dob_year' => 0,
            
'status' => '',
            
'status_date' => 0,
            
'status_profile_post_id' => 0,
            
'signature' => '',
            
'homepage' => '',
            
'location' => '',
            
'occupation' => '',
            
'following' => '',
            
'ignored' => '',
            
'csrf_token' => '',
            
'avatar_crop_x' => 0,
            
'avatar_crop_y' => 0,
            
'about' => '',
            
'custom_fields' => '',
            
'external_auth' => '',

            
// xf_user_option
            
'show_dob_year' => 0,
            
'show_dob_date' => 0,
            
'content_show_signature' => $options->guestShowSignatures,
            
'receive_admin_email' => 0,
            
'email_on_conversation' => 0,
            
'is_discouraged' => 0,
            
'default_watch_state' => '',
            
'alert_optout' => '',
            
'enable_rte' => 1,
            
'enable_flash_uploader' => 1,

            
// xf_user_privacy
            
'allow_view_profile' => 'everyone',
            
'allow_post_profile' => 'everyone',
            
'allow_send_personal_conversation' => 'everyone',
            
'allow_view_identities' => 'everyone',
            
'allow_receive_news_feed' => 'everyone',

            
// other tables/data
            
'csrf_token_page' => '',
            
'global_permission_cache' => ''
        
);

        
$userinfo $this->setPermissionsOnVisitorArray($userinfo);
        return 
$userinfo;
    }

    
/**
     * Sets the specified permissions (combination and permissions string) on visitor array.
     * Defaults to setting guest permissions.
     *
     * @param array $userinfo Visitor record
     *
     * @return array Visitor record with permissions
     */
    
public function setPermissionsOnVisitorArray(array $userinfo$permissionCombinationId false)
    {
        if (!
$permissionCombinationId)
        {
            
$permissionCombinationId self::$guestPermissionCombinationId;
        }

        
$userinfo['permission_combination_id'] = $permissionCombinationId;

        
$userinfo['global_permission_cache'] = $this->_getDb()->fetchOne('
            SELECT cache_value
            FROM xf_permission_combination
            WHERE permission_combination_id = ?
        '
$permissionCombinationId);

        return 
$userinfo;
    }

    
/**
     * Sets the permission info from the specified user ID into an array of user info
     * (likely for a visitor array).
     *
     * @param array $userInfo
     * @param integer $permUserId
     *
     * @return array User info with changed permissions
     */
    
public function setPermissionsFromUserId(array $userInfo$permUserId)
    {
        
$permUser $this->getUserById($permUserId, array(
            
'join' => self::FETCH_USER_PERMISSIONS
        
));
        if (
$permUser)
        {
            
$userInfo['permission_combination_id'] = $permUser['permission_combination_id'];
            
$userInfo['global_permission_cache'] = $permUser['global_permission_cache'];
        }

        return 
$userInfo;
    }

    
/**
     * Updates the session activity of a user.
     *
     * @param integer $userId
     * @param string $ip IP of visiting user
     * @param string $controllerName Last controller class that was invoked
     * @param string $action Last action that was invoked
     * @param string $viewState Either "valid" or "error"
     * @param array $inputParams List of special input params, to include to help get more info on current activity
     * @param integer|null $viewDate The timestamp of the last page view; defaults to now
     * @param string $robotKey
     */
    
public function updateSessionActivity($userId$ip$controllerName$action$viewState, array $inputParams$viewDate null$robotKey '')
    {
        
$userId intval($userId);
        
$ipNum XenForo_Helper_Ip::getBinaryIp(null$ip'');
        
$uniqueKey = ($userId $userId $ipNum);

        if (
$userId)
        {
            
$robotKey '';
        }

        if (!
$viewDate)
        {
            
$viewDate XenForo_Application::$time;
        }

        
$logParams = array();
        foreach (
$inputParams AS $paramKey => $paramValue)
        {
            if (!
strlen($paramKey) || $paramKey[0] == '_' || !is_scalar($paramValue))
            {
                continue;
            }

            
$logParams[] = "$paramKey=" urlencode($paramValue);
        }
        
$paramList implode('&'$logParams);
        
$paramList substr($paramList0100);

        
$controllerName substr($controllerName050);
        
$action substr($action050);

        try
        {
            
$this->_getDb()->query('
                INSERT INTO xf_session_activity
                    (user_id, unique_key, ip, controller_name, controller_action, view_state, params, view_date, robot_key)
                VALUES
                    (?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE
                    ip = VALUES(ip),
                    controller_name = VALUES(controller_name),
                    controller_action = VALUES(controller_action),
                    view_state = VALUES(view_state),
                    params = VALUES(params),
                    view_date = VALUES(view_date),
                    robot_key = VALUES(robot_key)
            '
, array($userId$uniqueKey$ipNum$controllerName$action$viewState$paramList$viewDate$robotKey));
        }
        catch (
Zend_Db_Exception $e) {} // ignore db errors here, not that important
    
}

    
/**
     * Deletes the session activity record for the specified user / IP address
     *
     * @param integer $userId
     * @param string $ip
     */
    
public function deleteSessionActivity($userId$ip)
    {
        
$userId intval($userId);
        
$ipNum XenForo_Helper_Ip::convertIpStringToBinary($ip);
        
$uniqueKey = ($userId $userId $ipNum);

        
$db $this->_getDb();
        
$db->delete('xf_session_activity''user_id = ' $db->quote($userId) . ' AND unique_key = ' $db->quote($uniqueKey));
    }

    
/**
     * Gets the latest (valid) user to join.
     *
     * @return array|false
     */
    
public function getLatestUser()
    {
        return 
$this->_getDb()->fetchRow($this->limitQueryResults('
            SELECT *
            FROM xf_user
            WHERE user_state = '
valid'
                 AND is_banned = 0
            ORDER BY register_date DESC
        '
1));
    }

    
/**
     * Fetch the most recently-registered users
     *
     * @param array $criteria
     * @param array $fetchOptions
     *
     * @return array User records
     */
    
public function getLatestUsers(array $criteria, array $fetchOptions = array())
    {
        
$fetchOptions['order'] = 'register_date';
        
$fetchOptions['direction'] = 'desc';

        return 
$this->getUsers($criteria$fetchOptions);
    }

    
/**
     * Fetch the most active users
     *
     * @param array $criteria
     * @param array $fetchOptions
     *
     * @return array User records
     */
    
public function getMostActiveUsers(array $criteria, array $fetchOptions = array())
    {
        
$fetchOptions['order'] = 'message_count';
        
$fetchOptions['direction'] = 'desc';

        return 
$this->getUsers($criteria$fetchOptions);
    }

    
/**
     * Gets the count of total users.
     *
     * @return integer
     */
    
public function countTotalUsers()
    {
        return 
$this->_getDb()->fetchOne('
            SELECT COUNT(*)
            FROM xf_user
            WHERE user_state = '
valid'
                 AND is_banned = 0
        '
);
    }

    
/**
     * Gets the user authentication record by user ID.
     *
     * @param integer $userId
     *
     * @return array|false
     */
    
public function getUserAuthenticationRecordByUserId($userId)
    {
        return 
$this->_getDb()->fetchRow('

            SELECT *
            FROM xf_user_authenticate
            WHERE user_id = ?

        '
$userId);
    }

    
/**
     * Returns an auth object based on an input userid
     *
     * @param integer Userid
     *
     * @return XenForo_Authentication_Abstract|false
     */
    
public function getUserAuthenticationObjectByUserId($userId)
    {
        
$authenticate $this->getUserAuthenticationRecordByUserId($userId);
        if (!
$authenticate)
        {
            return 
false;
        }

        
$auth XenForo_Authentication_Abstract::create($authenticate['scheme_class']);
        if (!
$auth)
        {
            return 
false;
        }

        
$auth->setData($authenticate['data']);
        return 
$auth;
    }

    
/**
     * Logs the given user in (as the visiting user). Exceptions are thrown on errors.
     *
     * @param string $nameOrEmail User name or email address
     * @param string $password
     * @param string $error Error string (by ref)
     *
     * @return integer|false User ID auth'd as; false on failure
     */
    
public function validateAuthentication($nameOrEmail$password, &$error '')
    {
        
$user $this->getUserByNameOrEmail($nameOrEmail);
        if (!
$user)
        {
            
$error = new XenForo_Phrase('requested_user_x_not_found', array('name' => $nameOrEmail));
            return 
false;
        }

        
$authentication $this->getUserAuthenticationObjectByUserId($user['user_id']);
        if (!
$authentication || !$authentication->authenticate($user['user_id'], $password))
        {
            
$error = new XenForo_Phrase('incorrect_password');
            return 
false;
        }

        
$upgraded $authentication->getUpgradedAuthenticationObject();
        if (
$upgraded)
        {
            
$userDw XenForo_DataWriter::create('XenForo_DataWriter_User'XenForo_DataWriter::ERROR_SILENT);
            
$userDw->setExistingData($user['user_id']);
            
$userDw->setOption(XenForo_DataWriter_User::OPTION_LOG_CHANGESfalse);
            
$userDw->setPassword($passwordfalse$upgraded);
            
$userDw->save();
        }

        return 
$user['user_id'];
    }

    
/**
     * Logs a user in based on their remember key from a cookie.
     *
     * @param integer $userId
     * @param string $rememberKey
     * @param array|false|null $auth User's auth record (retrieved if null)
     *
     * @return boolean
     */
    
public function loginUserByRememberKeyFromCookie($userId$rememberKey$auth null)
    {
        if (
$auth === null)
        {
            
$auth $this->getUserAuthenticationRecordByUserId($userId);
        }

        if (!
$auth || $this->prepareRememberKeyForCookie($auth['remember_key']) !== $rememberKey)
        {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Logs a user in based on the raw value of the remember cookie.
     *
     * @param string $userCookie
     *
     * @return false|integer
     */
    
public function loginUserByRememberCookie($userCookie)
    {
        if (!
$userCookie)
        {
            return 
false;
        }

        
$userCookieParts explode(','$userCookie);
        if (
count($userCookieParts) < 2)
        {
            return 
false;
        }

        
$userId intval($userCookieParts[0]);
        
$rememberKey $userCookieParts[1];
        if (!
$userId || !$rememberKey)
        {
            return 
false;
        }

        
$auth $this->getUserAuthenticationRecordByUserId($userId);
        
$loggedIn $this->loginUserByRememberKeyFromCookie($userId$rememberKey$auth);
        if (
$loggedIn)
        {
            return 
$userId;
        }
        else
        {
            return 
false;
        }
    }

    
/**
     * Sets the user remember cookie for the specified user ID.
     *
     * @param integer $userId
     * @param array|false|null $auth User's auth record (retrieved if null)
     *
     * @return boolean
     */
    
public function setUserRememberCookie($userId$auth null)
    {
        
$value $this->getUserRememberKeyForCookie($userId$auth);
        if (!
$value)
        {
            return 
false;
        }

        
XenForo_Helper_Cookie::setCookie('user'$value30 86400true);

        return 
true;
    }

    public function 
getUserRememberKeyForCookie($userId$auth null)
    {
        if (
$auth === null)
        {
            
$auth $this->getUserAuthenticationRecordByUserId($userId);
        }

        if (!
$auth)
        {
            return 
false;
        }

        return 
intval($userId) . ',' $this->prepareRememberKeyForCookie($auth['remember_key']);
    }

    
/**
     * Prepares the remember key for use in a cookie (or for comparison against the cookie).
     *
     * @param string $rememberKey Key from DB
     *
     * @return string
     */
    
public function prepareRememberKeyForCookie($rememberKey)
    {
        return 
sha1(XenForo_Application::get('config')->globalSalt $rememberKey);
    }

    public function 
getUserEmailConfirmKey(array $user)
    {
        return 
md5(XenForo_Application::get('config')->globalSalt $user['user_id'] . $user['email']);
    }

    
/**
     * Prepares a user record for display. Note that this may be called on incomplete guest records.
     *
     * @param array $user User info
     *
     * @return array Prepared user info
     */
    
public function prepareUser(array $user)
    {
        if (empty(
$user['user_group_id']))
        {
            
$user['display_style_group_id'] = self::$defaultGuestGroupId;
        }

        
$user['customFields'] = (!empty($user['custom_fields']) ? @unserialize($user['custom_fields']) : array());
        
$user['externalAuth'] = (!empty($user['external_auth']) ? @unserialize($user['external_auth']) : array());

        
// "trusted" user check - used to determine if no follow is enabled
        
$user['isTrusted'] = (!empty($user['user_id']) && (!empty($user['is_admin']) || !empty($user['is_moderator'])));

        if (
XenForo_Visitor::hasInstance())
        {
            
$user['isIgnored'] = XenForo_Visitor::getInstance()->isIgnoring($user['user_id']);
        }

        return 
$user;
    }

    
/**
     * Prepares the data needed for the simple user card-like output.
     *
     * @param array $user
     *
     * @return array
     */
    
public function prepareUserCard(array $user)
    {
        
$user['age'] = $this->_getUserProfileModel()->getUserAge($user);
        
$user['customFields'] = (!empty($user['custom_fields']) ? @unserialize($user['custom_fields']) : array());

        return 
$user;
    }

    
/**
     * Prepares a batch of user cards.
     *
     * @param array $users
     *
     * @return array
     */
    
public function prepareUserCards(array $users)
    {
        foreach (
$users AS &$user)
        {
            
$user $this->prepareUserCard($user);
        }

        return 
$users;
    }

    
/**
     * Inserts (or updates an existing) user group change set.
     *
     * @param integer $userId
     * @param string $key Unique identifier for change set
     * @param string|array $addGroups Comma delimited string or array of user groups to add
     *
     * @return boolean True on change success
     */
    
public function addUserGroupChange($userId$key$addGroups)
    {
        if (
is_array($addGroups))
        {
            
$addGroups implode(','$addGroups);
        }

        if (!
$addGroups)
        {
            return 
true;
        }

        
$oldGroups $this->getUserGroupChangesForUser($userId);

        
$newGroups $oldGroups;

        if (isset(
$newGroups[$key]) && !$addGroups)
        {
            
// already exists and we're removing the groups, so we can just remove the record
            
return $this->removeUserGroupChange($userId$key);
        }

        
$newGroups[$key] = $addGroups;

        
$db $this->_getDb();
        
XenForo_Db::beginTransaction($db);

        
$success $this->_applyUserGroupChanges($userId$oldGroups$newGroups);
        if (
$success)
        {
            
$db->query('
                INSERT INTO xf_user_group_change
                    (user_id, change_key, group_ids)
                VALUES
                    (?, ?, ?)
                ON DUPLICATE KEY UPDATE
                    group_ids = VALUES(group_ids)
            '
, array($userId$key$addGroups));

            
XenForo_Db::commit($db);
        }
        else
        {
            
XenForo_Db::rollback($db);
        }


        return 
$success;
    }

    
/**
     * Removes the specified user group change set.
     *
     * @param integer $userId
     * @param string $key Change set key
     *
     * @return boolean True on success
     */
    
public function removeUserGroupChange($userId$key)
    {
        
$oldGroups $this->getUserGroupChangesForUser($userId);
        if (!isset(
$oldGroups[$key]))
        {
            
// already removed?
            
return true;
        }

        
$newGroups $oldGroups;
        unset(
$newGroups[$key]);

        
$db $this->_getDb();
        
XenForo_Db::beginTransaction($db);

        
$success $this->_applyUserGroupChanges($userId$oldGroups$newGroups);
        if (
$success)
        {
            
$db->delete('xf_user_group_change',
                
'user_id = ' $db->quote($userId) . ' AND change_key = ' $db->quote($key)
            );

            
XenForo_Db::commit($db);
        }
        else
        {
            
XenForo_Db::rollback($db);
        }

        return 
$success;
    }

    public function 
removeUserGroupChangeLogByKey($key)
    {
        
$db $this->_getDb();
        
$db->delete('xf_user_group_change''change_key = ' $db->quote($key));
    }

    
/**
     * Gets the user group change sets for the specified user.
     *
     * @param integer $userId
     *
     * @return array [change key] => comma list of group IDs
     */
    
public function getUserGroupChangesForUser($userId)
    {
        return 
$this->_getDb()->fetchPairs('
            SELECT change_key, group_ids
            FROM xf_user_group_change
            WHERE user_id = ?
        '
$userId);
    }

    
/**
     * Applies a set of user group changes.
     *
     * @param integer $userId
     * @param array $oldGroupStrings Array of comma-delimited strings of existing (accounted for) user group change sets
     * @param array $newGroupStrings Array of comma-delimited strings for new list of user group change sets
     *
     * @return boolean
     */
    
protected function _applyUserGroupChanges($userId, array $oldGroupStrings, array $newGroupStrings)
    {
        
$oldGroups = array();
        foreach (
$oldGroupStrings AS $string)
        {
            
$oldGroups array_merge($oldGroupsexplode(','$string));
        }
        
$oldGroups array_unique($oldGroups);

        
$newGroups = array();
        foreach (
$newGroupStrings AS $string)
        {
            
$newGroups array_merge($newGroupsexplode(','$string));
        }
        
$newGroups array_unique($newGroups);

        
$addGroups array_diff($newGroups$oldGroups);
        
$removeGroups array_diff($oldGroups$newGroups);

        if (!
$addGroups && !$removeGroups)
        {
            return 
true;
        }

        
$dw XenForo_DataWriter::create('XenForo_DataWriter_User'XenForo_DataWriter::ERROR_SILENT);
        if (!
$dw->setExistingData($userId))
        {
            return 
false;
        }

        
$secondaryGroups explode(','$dw->get('secondary_group_ids'));
        if (
$removeGroups)
        {
            foreach (
$secondaryGroups AS $key => $secondaryGroup)
            {
                if (
in_array($secondaryGroup$removeGroups))
                {
                    unset(
$secondaryGroups[$key]);
                }
            }
        }
        if (
$addGroups)
        {
            
$secondaryGroups array_merge($secondaryGroups$addGroups);
        }

        
$dw->setSecondaryGroups($secondaryGroups);
        return 
$dw->save();
    }

    
/**
     * Determines if a user is a member of a particular user group
     *
     * @param array $user
     * @param integer|array $userGroupId either a single user group ID or an array thereof
     * @param boolean Also check secondary groups
     *
     * @return boolean
     */
    
public function isMemberOfUserGroup(array $user$userGroupId$includeSecondaryGroups true)
    {
        if (
is_array($userGroupId))
        {
            if (
in_array($user['user_group_id'], $userGroupId))
            {
                return 
true;
            }

            if (
$includeSecondaryGroups && array_intersect($userGroupIdexplode(','$user['secondary_group_ids'])))
            {
                return 
true;
            }
        }
        else
        {
            if (
$user['user_group_id'] == $userGroupId)
            {
                return 
true;
            }

            if (
$includeSecondaryGroups && strpos(",{$user['secondary_group_ids']},"",{$userGroupId},") !== false)
            {
                return 
true;
            }
        }

        return 
false;
    }

    public function 
isUserSuperAdmin(array $user)
    {
        
$superAdmins preg_split(
            
'#s*,s*#'XenForo_Application::get('config')->superAdmins,
            -
1PREG_SPLIT_NO_EMPTY
        
);
        return (
$user['is_admin'] && in_array($user['user_id'], $superAdmins));
    }

    
/**
     * Creates a new follower record for $userId following $followUserId(s)
     *
     * @param array Users being followed
     * @param boolean Check for and prevent duplicate followers
     * @param array $user User doing the following
     *
     * @return string Comma-separated list of all users now being followed by $userId
     */
    
public function follow(array $followUsers$dupeCheck true, array $user null)
    {
        if (
$user === null)
        {
            
$user XenForo_Visitor::getInstance();
        }

        
// if we have only a single user, build the multi-user array structure
        
if (isset($followUsers['user_id']))
        {
            
$followUsers = array($followUsers['user_id'] => $followUsers);
        }

        if (
$dupeCheck)
        {
            
$followUsers $this->removeDuplicateFollowUserIds($user['user_id'], $followUsers);
        }

        
$db $this->_getDb();
        
$errors false;

        
XenForo_Db::beginTransaction($db);

        foreach (
$followUsers AS $followUser)
        {
            if (
$user['user_id'] == $followUser['user_id'])
            {
                continue;
            }

            
$writer XenForo_DataWriter::create('XenForo_DataWriter_Follower');
            
$writer->setOption(XenForo_DataWriter_Follower::OPTION_POST_WRITE_UPDATE_USER_FOLLOWINGfalse);
            
$writer->set('user_id'$user['user_id']);
            
$writer->set('follow_user_id'$followUser['user_id']);
            
$success $writer->save();

            if (
$success && XenForo_Model_Alert::userReceivesAlert($followUser'user''following'))
            {
                
XenForo_Model_Alert::alert(
                    
$followUser['user_id'],
                    
$user['user_id'], $user['username'],
                    
'user'$followUser['user_id'],
                    
'following'
                
);
            }
        }

        
$return $this->updateFollowingDenormalizedValue($user['user_id']);

        
XenForo_Db::commit($db);

        return 
$return;
    }

    
/**
     * Deletes an existing follower record for $userId following $followUserId
     *
     * @param integer $followUserId User being followed
     * @param integer $userId User doing the following
     *
     * @return string Comma-separated list of all users now being followed by $userId
     */
    
public function unfollow($followUserId$userId null)
    {
        if (
$userId === null)
        {
            
$userId XenForo_Visitor::getUserId();
        }

        
$db $this->_getDb();

        
XenForo_Db::beginTransaction($db);

        
$writer XenForo_DataWriter::create('XenForo_DataWriter_Follower'XenForo_DataWriter::ERROR_SILENT);
        
$writer->setOption(XenForo_DataWriter_Follower::OPTION_POST_WRITE_UPDATE_USER_FOLLOWINGfalse);
        
$writer->setExistingData(array($userId$followUserId));
        
$writer->delete();

        
$value $this->updateFollowingDenormalizedValue($userId);

        
// delete alerts
        
$this->getModelFromCache('XenForo_Model_Alert')->deleteAlerts('user'$followUserId$userId'follow');

        
XenForo_Db::commit($db);

        return 
$value;
    }

    
/**
     * Compares an array of user IDs to be followed with the existing value and removes any duplicates
     * to prevent duplicate key errors on insertion
     *
     * @param integer $userId
     * @param array $newUsers (full user arrays)
     * @param string $existingUserIds '3,6,42,....'
     *
     * @return array
     */
    
public function removeDuplicateFollowUserIds($userId, array $newUsers$existingUserIds null)
    {
        if (
$existingUserIds === null)
        {
            
$existingUserIds $this->getFollowingDenormalizedValue($userId);
        }

        
$existingUserIds explode(','$existingUserIds);

        foreach (
$newUsers AS $i => $newUser)
        {
            if (
in_array($newUser['user_id'], $existingUserIds))
            {
                unset(
$newUsers[$i]);
            }
        }

        return 
$newUsers;
    }

    
/**
     * Fetches a single user-following-user record.
     *
     * @param integer|array $userId - the user doing the following
     * @param integer|array $followUserId - the user being followed
     *
     * @return array
     */
    
public function getFollowRecord($userId$followUserId)
    {
        return 
$this->_getDb()->fetchRow('

            SELECT *
            FROM xf_user_follow
            WHERE user_id = ?
            AND follow_user_id = ?

        '
, array(
            
$this->getUserIdFromUser($userId),
            
$this->getUserIdFromUser($followUserId)
        ));
    }

    
/**
     * Gets an array of all users being followed by the specified user
     *
     * @param integer|array $userId|$user
     * @param integer $maxResults (0 = all)
     * @param string $orderBy
     *
     * @return array
     */
    
public function getFollowedUserProfiles($userId$maxResults 0$orderBy 'user.username')
    {
        
$sql '
            SELECT
                user.*,
                user_profile.*,
                user_option.*
            FROM xf_user_follow AS user_follow
            INNER JOIN xf_user AS user ON
                (user.user_id = user_follow.follow_user_id AND user.is_banned = 0)
            INNER JOIN xf_user_profile AS user_profile ON
                (user_profile.user_id = user.user_id)
            INNER JOIN xf_user_option AS user_option ON
                (user_option.user_id = user.user_id)
            WHERE user_follow.user_id = ?
            ORDER BY ' 
$orderBy '
        '
;

        if (
$maxResults)
        {
            
$sql $this->limitQueryResults($sql$maxResults);
        }

        return 
$this->fetchAllKeyed($sql'user_id'$this->getUserIdFromUser($userId));
    }

    
/**
     * Gets users followed by the user ID.
     *
     * @param integer $userId
     * @param array $fetchOptions
     *
     * @return array
     */
    
public function getFollowedUsers($userId, array $fetchOptions = array())
    {
        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');
        
$limitOptions $this->prepareLimitFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed($this->limitQueryResults(
            
'
                SELECT user.*,
                    user_profile.*,
                    user_option.*
                FROM xf_user_follow AS user_follow
                INNER JOIN xf_user AS user ON
                    (user.user_id = user_follow.follow_user_id AND user.is_banned = 0)
                INNER JOIN xf_user_profile AS user_profile ON
                    (user_profile.user_id = user.user_id)
                INNER JOIN xf_user_option AS user_option ON
                    (user_option.user_id = user.user_id)
                WHERE user_follow.user_id = ?
                ' 
$orderClause '
            '
$limitOptions['limit'], $limitOptions['offset']
        ), 
'user_id'$userId);
    }

    
/**
     * Generates the denormalized, comma-separated version of a user's following
     *
     * @param $userId
     *
     * @return string
     */
    
public function getFollowingDenormalizedValue($userId)
    {
        return 
implode(','$this->_getDb()->fetchCol('

            SELECT follow_user_id
            FROM xf_user_follow AS user_follow
            INNER JOIN xf_user AS user ON
                (user.user_id = user_follow.follow_user_id)
            WHERE user_follow.user_id = ?
            ORDER BY user.username

        '
$this->getUserIdFromUser($userId)));
    }

    
/**
     * Returns whether or not the specified user is being followed by the follower
     *
     * @param integer $userId User being followed
     * @param array $follower User doing the following
     *
     * @return boolean
     */
    
public function isFollowing($userId, array $follower null)
    {
        if (
$follower === null)
        {
            
$follower XenForo_Visitor::getInstance();
        }

        if (!
$follower['user_id'] || $follower['user_id'] == $userId)
        {
            return 
false;
        }

        return (
strpos(",{$follower['following']},"",{$userId},") !== false);
    }

    
/**
     * Updates the denormalized, comma-separated version of a user's following.
     * Will query for the value if it is not provided
     *
     * @param integer|array $userId|$user
     * @param string Denormalized following value
     *
     * @return string
     */
    
public function updateFollowingDenormalizedValue($userId$following false)
    {
        
$userId $this->getUserIdFromUser($userId);

        if (
$following === false)
        {
            
$following $this->getFollowingDenormalizedValue($userId);
        }

        
$this->update($userId'following'$following);

        return 
$following;
    }

    
/**
     * Gets the user information for all users following the specified user.
     *
     * @param integer $userId
     * @param integer $maxResults (0 = all)
     * @param string $orderBy
     *
     * @return array Format: [user id] => following user info
     */
    
public function getUsersFollowingUserId($userId$maxResults 0$orderBy 'user.username')
    {
        
$sql '
            SELECT user.*,
                user_profile.*,
                user_option.*
            FROM xf_user_follow AS user_follow
            INNER JOIN xf_user AS user ON
                (user.user_id = user_follow.user_id AND user.is_banned = 0)
            INNER JOIN xf_user_profile AS user_profile ON
                (user_profile.user_id = user.user_id)
            INNER JOIN xf_user_option AS user_option ON
                (user_option.user_id = user.user_id)
            WHERE user_follow.follow_user_id = ?
            ORDER BY ' 
$orderBy '
        '
;

        if (
$maxResults)
        {
            
$sql $this->limitQueryResults($sql$maxResults);
        }

        return 
$this->fetchAllKeyed($sql'user_id'$userId);
    }

    
/**
     * Gets users following the specified user ID.
     *
     * @param integer $userId
     * @param array $fetchOptions
     *
     * @return array
     */
    
public function getUsersFollowing($userId, array $fetchOptions = array())
    {
        
$orderClause $this->prepareUserOrderOptions($fetchOptions'user.username');
        
$limitOptions $this->prepareLimitFetchOptions($fetchOptions);

        return 
$this->fetchAllKeyed($this->limitQueryResults(
            
'
                SELECT user.*,
                    user_profile.*,
                    user_option.*
                FROM xf_user_follow AS user_follow
                INNER JOIN xf_user AS user ON
                    (user.user_id = user_follow.user_id AND user.is_banned = 0)
                INNER JOIN xf_user_profile AS user_profile ON
                    (user_profile.user_id = user.user_id)
                INNER JOIN xf_user_option AS user_option ON
                    (user_option.user_id = user.user_id)
                WHERE user_follow.follow_user_id = ?
                ' 
$orderClause '
            '
$limitOptions['limit'], $limitOptions['offset']
        ), 
'user_id'$userId);
    }

    
/**
     * Gets the count of users following the specified user.
     *
     * @param integer $userId
     *
     * @return array Format: [user id] => following user info
     */
    
public function countUsersFollowingUserId($userId)
    {
        return 
$this->_getDb()->fetchOne('
            SELECT COUNT(*)
            FROM xf_user_follow
            WHERE follow_user_id = ?
        '
$userId);
    }

    
/**
     * Rebuilds the custom field cache for a specific user, using the canonical data.
     *
     * @param integer $userId
     */
    
public function rebuildCustomFieldCache($userId)
    {
        
$db $this->_getDb();

        
$cache $this->_getFieldModel()->getUserFieldValues($userId);

        
$db->update('xf_user_profile',
            array(
'custom_fields' => serialize($cache)),
            
'user_id = '$db->quote($userId)
        );
    }

    
/**
     * Returns an array containing the user ids found from the complete result given the range specified,
     * along with the total number of users found.
     *
     * @param integer Find users with user_id greater than...
     * @param integer Maximum users to return at once
     *
     * @return array
     */
    
public function getUserIdsInRange($start$limit)
    {
        
$db $this->_getDb();

        return 
$db->fetchCol($db->limit('
            SELECT user_id
            FROM xf_user
            WHERE user_id > ?
            ORDER BY user_id
        '
$limit), $start);
    }

    
/**
     * Returns the number of unread alerts belonging to a user - following fresh recalculation
     *
     * @param integer $userId
     *
     * @return integer
     */
    
public function getUnreadAlertsCount($userId)
    {
        return 
$this->_getDb()->fetchOne('
            SELECT COUNT(*) AS total
            FROM xf_user_alert
            WHERE alerted_user_id = ?
                AND view_date = 0
        '
$userId);
    }

    
/**
     * Determines if the user has permission to bypass users' privacy preferences, including online status and activity feed
     *
     * @param string $errorPhraseKey
     * @param array $viewingUser
     *
     * @return boolean
     */
    
public function canBypassUserPrivacy(&$errorPhraseKey '', array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        if (
XenForo_Permission::hasPermission($viewingUser['permissions'], 'general''bypassUserPrivacy'))
        {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Determines if permissions are sufficient to view on the specified
     * user's online status.
     *
     * @param array $user User being viewed
     * @param string $errorPhraseKey Returned by ref. Phrase key of more specific error
     * @param array|null $viewingUser Viewing user ref
     *
     * @return boolean
     */
    
public function canViewUserOnlineStatus(array $user, &$errorPhraseKey '', array $viewingUser null)
    {
        if (!
$user['user_id'] || !$user['last_activity'])
        {
            return 
false;
        }
        else if (
$user['visible'])
        {
            return 
true;
        }

        
$this->standardizeViewingUserReference($viewingUser);

        if (
$user['user_id'] == $viewingUser['user_id'])
        {
            
// can always view own
            
return true;
        }

        return 
$this->canBypassUserPrivacy($errorPhraseKey$viewingUser);
    }

    
/**
     * Determines if permissions are sufficient to warn the given user.
     *
     * @param array $user User being viewed
     * @param string $errorPhraseKey Returned by ref. Phrase key of more specific error
     * @param array|null $viewingUser Viewing user ref
     *
     * @return boolean
     */
    
public function canWarnUser(array $user, &$errorPhraseKey '', array $viewingUser null)
    {
        if (!empty(
$user['is_admin']) || !empty($user['is_moderator']) || empty($user['user_id']))
        {
            return 
false;
        }

        
$this->standardizeViewingUserReference($viewingUser);

        return (
$viewingUser['user_id'] && XenForo_Permission::hasPermission($viewingUser['permissions'], 'general''warn'));
    }

    
/**
     * Checks that the viewing user may report the specified user
     *
     * @param array $user User being viewed
     * @param string $errorPhraseKey Returned by ref. Phrase key of more specific error
     * @param array|null $viewingUser Viewing user ref
     *
     * @return boolean
     */
    
public function canReportUser(array $user, &$errorPhraseKey '', array $viewingUser null)
    {
        if (
$user['is_staff'])
        {
            return 
false;
        }

        return 
$this->canReportContent($errorPhraseKey$viewingUser);
    }

    
/**
     * Determines if the viewing user can edit a user's basic details
     * (moderator-level permission for editing)
     *
     * @param array $user
     * @param string $errorPhraseKey
     * @param array|null $viewingUser
     *
     * @return boolean
     */
    
public function canEditUser(array $user, &$errorPhraseKey '', array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        if (!
$viewingUser['user_id'])
        {
            return 
false;
        }

        
$viewerIsSuperAdmin $this->isUserSuperAdmin($viewingUser);
        if (
$viewerIsSuperAdmin)
        {
            return 
true;
        }

        if (
$user['is_admin'] && $this->isUserSuperAdmin($user) && !$viewerIsSuperAdmin)
        {
            return 
false;
        }

        if (
$viewingUser['is_admin'])
        {
            
$adminPermissions XenForo_Model::create('XenForo_Model_Admin')->getAdminPermissionCacheForUser(
                
$viewingUser['user_id']
            );
            if (
$adminPermissions && !empty($adminPermissions['user']))
            {
                return 
true;
            }
        }

        if (
$user['is_admin'] || $user['is_moderator'] || $user['is_staff'])
        {
            
// moderators can't edit admins/mods/staff
            
return false;
        }

        return 
XenForo_Permission::hasPermission($viewingUser['permissions'], 'general''editBasicProfile');
    }

    
/**
     * Determines if the viewing user can start a conversation with the given user.
     * Does not check standard conversation permissions.
     *
     * @param array $user
     * @param string $errorPhraseKey
     * @param array|null $viewingUser
     *
     * @return boolean
     */
    
public function canStartConversationWithUser(array $user, &$errorPhraseKey '', array $viewingUser null)
    {
        if (!
$user['user_id'])
        {
            return 
false;
        }

        return 
$this->getModelFromCache('XenForo_Model_Conversation')->canStartConversationWithUser(
            
$user$errorPhraseKey$viewingUser
        
);
    }

    
/**
     * Determines if the viewing user can view IPs logged with posts, profile posts etc.
     *
     * @param string $errorPhraseKey
     * @param array|null $viewingUser
     *
     * @return boolean
     */
    
public function canViewIps(&$errorPhraseKey '', array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        return (
$viewingUser['user_id'] && XenForo_Permission::hasPermission($viewingUser['permissions'], 'general''viewIps'));
    }

    
/**
     * Determines if a user can report the specified content
     *
     * @param string $errorPhraseKey
     * @param array|null $viewingUser
     *
     * @return boolean
     */
    
public function canReportContent(&$errorPhraseKey '', array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        if (!
$viewingUser['user_id'] || !XenForo_Permission::hasPermission($viewingUser['permissions'], 'general''report'))
        {
            
$errorPhraseKey 'you_may_not_report_this_content';
            return 
false;
        }

        return 
true;
    }

    
/**
     * Determines if a user can view the warnings
     *
     * @param string $errorPhraseKey
     * @param array|null $viewingUser
     *
     * @return boolean
     */
    
public function canViewWarnings(&$errorPhraseKey '', array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        if (!
$viewingUser['user_id'] || !XenForo_Permission::hasPermission($viewingUser['permissions'], 'general''viewWarning'))
        {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Determines if the viewing user can start conversations in general.
     *
     * @param string $errorPhraseKey
     * @param array|null $viewingUser
     *
     * @return boolean
     */
    
public function canStartConversations(&$errorPhraseKey '', array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        if (!
$viewingUser['user_id'])
        {
            return 
false;
        }

        if (
$viewingUser['user_state'] != 'valid')
        {
            return 
false;
        }

        if (
XenForo_Permission::hasPermission($viewingUser['permissions'], 'conversation''start'))
        {
            
$maxRecipients XenForo_Permission::hasPermission($viewingUser['permissions'], 'conversation''maxRecipients');
            return (
$maxRecipients == -|| $maxRecipients 0);
        }

        return 
false;
    }

    
/**
     * Determines if the viewing user passes the specified privacy check.
     * This must include the following status for the viewing user.
     *
     * @param string $privacyRequirement The required privacy: everyone, none, members, followed
     * @param array $user User info, including following status for viewing user
     * @param array|null $viewingUser Viewing user ref
     * @return unknown_type
     */
    
public function passesPrivacyCheck($privacyRequirement, array $user, array $viewingUser null)
    {
        
$this->standardizeViewingUserReference($viewingUser);

        if (!isset(
$user['following_' $viewingUser['user_id']]) && !isset($user['following']))
        {
            throw new 
XenForo_Exception('Missing following state for user ID ' $viewingUser['user_id'] . ' in user ' $user['user_id']);
        }

        if (
$user['user_id'] == $viewingUser['user_id'])
        {
            return 
true;
        }

        if (
$this->canBypassUserPrivacy($null$viewingUser))
        {
            return 
true;
        }

        switch (
$privacyRequirement)
        {
            case 
'everyone': return true;
            case 
'none':     return false;
            case 
'members':  return ($viewingUser['user_id'] > 0);

            case 
'followed':
                if (isset(
$user['following_' $viewingUser['user_id']]))
                {
                    return (
$user['following_' $viewingUser['user_id']] > 0);
                }
                else if (!empty(
$user['following']))
                {
                    return 
in_array($viewingUser['user_id'], explode(','$user['following']));
                }
                else
                {
                    return 
false;
                }


            default:
                return 
true;
        }
    }

    
/**
     * Fetches the logged registration IP addresses for the specified user, if available.
     *
     * @param integer $userId
     *
     * @return array [ register: string, account-confirmation: string ]
     */
    
public function getRegistrationIps($userId)
    {
        
$ips $this->_getDb()->fetchPairs('
            SELECT action, ip
            FROM xf_ip
            WHERE user_id = ?
            AND content_type = '
user'
            AND action IN('
register', 'account-confirmation')
        '
$userId);

        return 
array_map(array('XenForo_Helper_Ip''convertIpBinaryToString'), $ips);
    }

    
/**
     * Determines whether or not the specified user may have the spam cleaner applied against them.
     *
     * @param array $user
     * @param string|array Error phrase key - may become an array if the phrase requires parameters
     *
     * @return boolean
     */
    
public function couldBeSpammer(array $user, &$errorKey '')
    {
        
// self
        
if ($user['user_id'] == XenForo_Visitor::getUserId())
        {
            
$errorKey 'sorry_dave';
            return 
false;
        }

        
// staff
        
if ($user['is_admin'] || $user['is_moderator'])
        {
            
$errorKey 'spam_cleaner_no_admins_or_mods';
            return 
false;
        }

        
$criteria XenForo_Application::get('options')->spamUserCriteria;

        if (
$criteria['message_count'] && $user['message_count'] > $criteria['message_count'])
        {
            
$errorKey = array('spam_cleaner_too_many_messages''message_count' => $criteria['message_count']);
            return 
false;
        }

        if (
$criteria['register_date'] && $user['register_date'] < (XenForo_Application::$time $criteria['register_date'] * 86400))
        {
            
$errorKey = array('spam_cleaner_registered_too_long''register_days' => $criteria['register_date']);
            return 
false;
        }

        if (
$criteria['like_count'] && $user['like_count'] > $criteria['like_count'])
        {
            
$errorKey = array('spam_cleaner_too_many_likes''like_count' => $criteria['like_count']);
            return 
false;
        }

        return 
true;
    }

    
/**
     * Bans a user or updates an existing ban.
     *
     * @param integer $userId ID of user to ban
     * @param integer $endDate Date at which ban will be lifted. Use XenForo_Model_User::PERMANENT_BAN for a permanent ban.
     * @param $reason
     * @param $update
     * @param $errorKey
     * @param $viewingUser
     * @param $triggered
     *
     * @return boolean
     */
    
public function ban($userId$endDate$reason$update false, &$errorKey null, array $viewingUser null$triggered false)
    {
        if (
$endDate XenForo_Application::$time && $endDate !== self::PERMANENT_BAN)
        {
            
$errorKey 'please_enter_a_date_in_the_future';
            return 
false;
        }

        
$this->standardizeViewingUserReference($viewingUser);

        
$dw XenForo_DataWriter::create('XenForo_DataWriter_UserBan');
        if (
$update)
        {
            
$dw->setExistingData($userId);
        }
        else
        {
            
$dw->set('user_id'$userId);
            
$dw->set('ban_user_id'$viewingUser['user_id']);
        }

        
$dw->set('end_date'$endDate);
        
$dw->set('user_reason'$reason);
        if (
$triggered || $dw->isChanged('end_date'))
        {
            
$dw->set('triggered'$triggered 0);
        }
        
$dw->preSave();

        if (
$dw->hasErrors())
        {
            
$errors $dw->getErrors();
            
$errorKey reset($errors);
            return 
false;
        }

        
$dw->save();
        return 
true;
    }

    
/**
     * Lifts the ban on the specified user
     *
     * @param integer $userId
     *
     * @return boolean
     */
    
public function liftBan($userId)
    {
        
$dw XenForo_DataWriter::create('XenForo_DataWriter_UserBan'XenForo_DataWriter::ERROR_SILENT);
        
$dw->setExistingData($userId);
        return 
$dw->delete();
    }

    
/**
     * Gets the date of the earliest user registration
     *
     * @return integer
     */
    
public function getEarliestRegistrationDate()
    {
        return (int)
$this->_getDb()->fetchOne('SELECT MIN(register_date) FROM xf_user');
    }

    
/**
     * Returns true if the specified user ID or user name is in the ignored (cache) of the given user.
     *
     * @param array $user
     * @param integer|string User ID or user name
     *
     * @return array|boolean
     */
    
public function isUserIgnored(array $user$ignoredUser)
    {
        if (empty(
$user['ignored']) || !$ignoredUser)
        {
            return 
false;
        }

        
$userId $user['user_id'];

        if (!isset(
$this->_ignoreCache[$userId]))
        {
            
$this->_ignoreCache[$userId] = unserialize($user['ignored']);
        }

        if (
is_int($ignoredUser) && isset($this->_ignoreCache[$userId][$ignoredUser]))
        {
            return array(
$ignoredUser$this->_ignoreCache[$userId][$ignoredUser]);
        }

        if (
is_string($ignoredUser))
        {
            
$ignoredUserId array_search($ignoredUser$this->_ignoreCache[$userId]);

            if (
$ignoredUserId !== false)
            {
                return array(
$ignoredUserId$this->_ignoreCache[$userId][$ignoredUserId]);
            }
        }

        return 
false;
    }

    
/**
     * List of user IDs/names to be changed when appropriate (name changes,
     * user deletes, user merges).
     *
     * Key is the table name. Value is an array of arrays. Inner array can have 1-3 elements:
     * 0. user_id column name
     * 1. username column name (unspecified/false if there isn't one)
     * 2. boolean to control whether this is updated when deleting a user (unspecified defaults to true).
     *
     * Delete updates should only be turned off when the user_id record is absolutely needed or the data has already been removed.
     *
     * @var array
     */
    
public static $userContentChanges = array(
        
'xf_admin_log' => array(array('user_id'falsefalse)),
        
'xf_attachment_data' => array(array('user_id')),
        
'xf_conversation_master' => array(array('user_id''username'), array('last_message_user_id''last_message_username')),
        
'xf_conversation_message' => array(array('user_id''username')),
        
'xf_conversation_recipient' => array(array('user_id'falsefalse)),
        
'xf_conversation_user' => array(array('owner_user_id'falsefalse), array('last_message_user_id''last_message_username')),
        
'xf_deletion_log' => array(array('delete_user_id''delete_username')),
        
'xf_edit_history' => array(array('edit_user_id')),
        
'xf_forum' => array(array('last_post_user_id''last_post_username')),
        
'xf_ip' => array(array('user_id')),
        
'xf_liked_content' => array(array('like_user_id'), array('content_user_id')),
        
'xf_news_feed' => array(array('user_id''username'false)),
        
'xf_poll_vote' => array(array('user_id'falsefalse)),
        
'xf_post' => array(array('user_id''username')),
        
'xf_profile_post' => array(array('profile_user_id'falsefalse), array('user_id''username')),
        
'xf_profile_post_comment' => array(array('user_id''username')),
        
'xf_report' => array(array('content_user_id'), array('last_modified_user_id''last_modified_username')),
        
'xf_report_comment' => array(array('user_id''username')),
        
'xf_spam_cleaner_log' => array(array('user_id''username'), array('applying_user_id''applying_username')),
        
'xf_thread' => array(array('user_id''username'), array('last_post_user_id''last_post_username')),
        
'xf_thread_user_post' => array(array('user_id')),
        
'xf_thread_watch' => array(array('user_id')),
        
'xf_user_alert' => array(array('user_id''username')),
        
'xf_user_follow' => array(array('user_id'falsefalse), array('follow_user_id'falsefalse)),
        
'xf_user_ignored' => array(array('user_id'falsefalse), array('ignored_user_id'falsefalse)),
        
'xf_user_trophy' => array(array('user_id')),
        
'xf_user_upgrade_active' => array(array('user_id'falsefalse)),
        
'xf_user_upgrade_expired' => array(array('user_id'falsefalse)),
        
'xf_warning' => array(array('user_id'), array('warning_user_id')),
    );

    
/**
     * Updates existing content with a new username, or updated user ID
     *
     * @param integer $existingUserId
     * @param string $newUserName
     * @param string $oldUserName
     * @param integer $newUserId (set if the user ownership has changed)
     */
    
public function changeContentUser($existingUserId$newUserName null$oldUserName null$newUserId null)
    {
        if (
$existingUserId === $newUserId)
        {
            
$newUserId null;
        }

        if (
$newUserName === null && $newUserId === null)
        {
            return;
        }

        
$db $this->_getDb();

        
XenForo_Db::beginTransaction($db);

        foreach (
self::$userContentChanges AS $table => $changes)
        {
            foreach (
$changes AS $change)
            {
                
$userIdCol $change[0];
                
$userNameCol = isset($change[1]) ? $change[1] : false;
                
$updateEmptyUserId = isset($change[2]) ? $change[2] : true;

                
$update = array();
                if (
$newUserId !== null && ($updateEmptyUserId || $newUserId))
                {
                    
$update[] = "$userIdCol = " $db->quote($newUserId);
                }
                if (
$newUserName !== null && $userNameCol)
                {
                    
$update[] = "$userNameCol = " $db->quote($newUserName);
                }
                if (
$update)
                {
                    
$db->query('
                        UPDATE IGNORE ' 
$table ' SET
                            ' 
implode(','$update) . '
                        WHERE ' 
$userIdCol ' = ?
                    '
$existingUserId);
                }
            }
        }

        if (
$newUserName || $newUserId !== null)
        {
            
$updatedUserId = ($newUserId !== null) ? $newUserId $existingUserId;
            
$updatedUsername $newUserName $newUserName $oldUserName;

            
$likeHandlers $this->getModelFromCache('XenForo_Model_Like')->getLikeHandlers();
            foreach (
$likeHandlers AS $contentType => $likeHandler)
            {
                
$likeHandler->batchUpdateContentUser($existingUserId$updatedUserId$oldUserName$updatedUsername);
            }
        }

        
XenForo_Db::commit($db);
    }

    
/**
     * Rebuilds the user moderation queue cache.
     *
     * @return array Cache, [total, lastModifiedDate]
     */
    
public function rebuildUserModerationQueueCache()
    {
        
$cache = array(
            
'total' => $this->countUsers(array('user_state' => 'moderated')),
            
'lastModifiedDate' => XenForo_Application::$time
        
);

        
$this->_getDataRegistryModel()->set('userModerationCounts'$cache);

        return 
$cache;
    }

    public function 
mergeUsers(array $target, array $source)
    {
        if (
$target['user_id'] == $source['user_id'])
        {
            return 
true;
        }

        
$dw XenForo_DataWriter::create('XenForo_DataWriter_User'XenForo_DataWriter::ERROR_SILENT);
        if (
$dw->setExistingData($target))
        {
            
XenForo_Db::beginTransaction();

            
// these are going to end up being the same person
            // and you can't like your own content so delete them
            
$this->_getDb()->query("
                DELETE FROM xf_liked_content
                WHERE like_user_id = ?
                    AND content_user_id = ?
            "
, array($source['user_id'], $target['user_id']));

            
$dw->set('message_count'$dw->get('message_count') + $source['message_count']);
            
$dw->set('like_count'$dw->get('like_count') + $source['like_count']); // this isn't 100% perfect
            
$dw->set('conversations_unread'$dw->get('conversations_unread') + $source['conversations_unread']);
            
$dw->set('alerts_unread'$dw->get('alerts_unread') + $source['alerts_unread']);
            
$dw->set('warning_points'$dw->get('warning_points') + $source['warning_points']);
            
$dw->set('register_date'min($dw->get('register_date'), $source['register_date']));
            
$dw->set('last_activity'max($dw->get('last_activity'), $source['last_activity']));
            
$dw->save();

            
$this->changeContentUser($source['user_id'], $target['username'], $source['username'], $target['user_id']);

            
$deleteDw XenForo_DataWriter::create('XenForo_DataWriter_User'XenForo_DataWriter::ERROR_SILENT);
            
$deleteDw->setExistingData($source);
            
$deleteDw->delete();

            
$this->getModelFromCache('XenForo_Model_Trophy')->updateTrophyPointsForUser($target['user_id']);

            
// this will survive the user delete - anything left over is where both users
            // were in the same conversation so we can remove the old records
            
$this->_getDb()->query("
                DELETE FROM xf_conversation_recipient
                WHERE user_id = ?
            "
$source['user_id']);

            
XenForo_Db::commit();

            return 
true;
        }
        else
        {
            return 
false;
        }
    }

    
/**
     * @see XenForo_Model_UserChangeLog::logChanges
     */
    
public function logChanges($userId, array $changedFields$editUserId null)
    {
        return 
$this->getModelFromCache('XenForo_Model_UserChangeLog')->logChanges($userId$changedFields$editUserId);
    }

    
/**
     * @return XenForo_Model_UserProfile
     */
    
protected function _getUserProfileModel()
    {
        return 
$this->getModelFromCache('XenForo_Model_UserProfile');
    }

    
/**
     * @return XenForo_Model_UserField
     */
    
protected function _getFieldModel()
    {
        return 
$this->getModelFromCache('XenForo_Model_UserField');
    }

    
/**
     * @return XenForo_Model_Ip
     */
    
protected function _getIpModel()
    {
        return 
$this->getModelFromCache('XenForo_Model_Ip');
    }
}
Онлайн: 2
Реклама