<?php
namespace XF\Entity;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
use function array_slice, count, in_array, is_array;
/**
* COLUMNS
* @property int|null $user_id
* @property string $username
* @property int $username_date
* @property int $username_date_visible
* @property string $email
* @property int $style_id
* @property int $language_id
* @property string $timezone
* @property bool $visible
* @property bool $activity_visible
* @property int $user_group_id
* @property array $secondary_group_ids
* @property int $display_style_group_id
* @property int $permission_combination_id_
* @property int $message_count
* @property int $question_solution_count
* @property int $alerts_unviewed
* @property int $alerts_unread
* @property int $conversations_unread
* @property int $register_date
* @property int $last_activity_
* @property int|null $last_summary_email_date
* @property int $trophy_points
* @property int $avatar_date
* @property int $avatar_width
* @property int $avatar_height
* @property bool $avatar_highdpi
* @property string $gravatar
* @property string $user_state
* @property string $security_lock
* @property bool $is_moderator
* @property bool $is_admin
* @property bool $is_staff
* @property bool $is_banned
* @property int $reaction_score
* @property int $vote_score
* @property string $custom_title
* @property int $warning_points
* @property string $secret_key
* @property int $privacy_policy_accepted
* @property int $terms_accepted
*
* GETTERS
* @property \XF\PermissionSet $PermissionSet
* @property int $permission_combination_id
* @property bool $is_super_admin
* @property int $last_activity
* @property string $email_confirm_key
* @property int $warning_count
* @property int $next_allowed_username_change
*
* RELATIONS
* @property \XF\Entity\Admin $Admin
* @property \XF\Entity\UserAuth $Auth
* @property \XF\Mvc\Entity\AbstractCollection|\XF\Entity\UserConnectedAccount[] $ConnectedAccounts
* @property \XF\Entity\UserOption $Option
* @property \XF\Entity\PermissionCombination $PermissionCombination
* @property \XF\Entity\UserProfile $Profile
* @property \XF\Entity\UserPrivacy $Privacy
* @property \XF\Entity\UserBan $Ban
* @property \XF\Entity\UserReject $Reject
* @property \XF\Entity\SessionActivity $Activity
* @property \XF\Entity\ApprovalQueue $ApprovalQueue
* @property \XF\Mvc\Entity\AbstractCollection|\XF\Entity\UserFollow[] $Following
* @property \XF\Entity\UsernameChange $PendingUsernameChange
* @property \XF\Entity\PreRegAction $PreRegAction
*/
class User extends Entity implements LinkableInterface
{
const GROUP_GUEST = 1;
const GROUP_REG = 2;
const GROUP_ADMIN = 3;
const GROUP_MOD = 4;
/************************* HELPERS & GETTERS ***************************/
public function getAbstractedCustomAvatarPath($size)
{
$userId = $this->user_id;
return sprintf('data://avatars/%s/%d/%d.jpg',
$size,
floor($userId / 1000),
$userId
);
}
public function getAvatarType()
{
if ($this->gravatar)
{
return 'gravatar';
}
else if ($this->avatar_date)
{
return 'custom';
}
else
{
return 'default';
}
}
public function getAvatarUrl($sizeCode, $forceType = null, $canonical = false)
{
$app = $this->app();
$sizeMap = $app->container('avatarSizeMap');
if (!isset($sizeMap[$sizeCode]))
{
// Always fallback to 's' in the event of an unknown size (e.g. 'xs', 'xxs' etc.)
$sizeCode = 's';
}
if ($this->gravatar && $forceType != 'custom')
{
return $this->getGravatarUrl($sizeCode);
}
else if ($this->avatar_date)
{
$group = floor($this->user_id / 1000);
return $app->applyExternalDataUrl(
"avatars/{$sizeCode}/{$group}/{$this->user_id}.jpg?{$this->avatar_date}",
$canonical
);
}
else
{
return null;
}
}
public function getAvatarUrl2x($sizeCode, $forceType = null, $canonical = false)
{
$sizeMap = $this->app()->container('avatarSizeMap');
switch ($sizeCode)
{
case 'xs':
case 's':
if ($this->avatarSupportsSize($sizeMap['m']))
{
return $this->getAvatarUrl('m', $forceType, $canonical);
}
break;
case 'm':
if ($this->avatarSupportsSize($sizeMap['l']))
{
return $this->getAvatarUrl('l', $forceType, $canonical);
}
break;
case 'l':
if ($this->avatar_highdpi || $this->gravatar)
{
return $this->getAvatarUrl('h', $forceType, $canonical);
}
break;
}
return '';
}
protected function avatarSupportsSize($size)
{
if ($this->gravatar)
{
// We can always support 2x gravatars
return true;
}
return ($this->avatar_date && $this->avatar_width >= $size && $this->avatar_height >= $size);
}
public function getGravatarUrl($sizeCode, $email = null)
{
$sizeMap = $this->app()->container('avatarSizeMap');
if (!isset($sizeMap[$sizeCode]))
{
$sizeCode = 's';
}
if ($email === null)
{
$email = $this->gravatar ?: $this->email;
}
$md5 = md5(strtolower(trim($email)));
$size = $sizeMap[$sizeCode];
return "https://secure.gravatar.com/avatar/{$md5}?s={$size}";
}
public function isMemberOf($userGroupId)
{
if ($userGroupId instanceof UserGroup)
{
$userGroupId = $userGroupId->user_group_id;
}
if (!$userGroupId)
{
return false;
}
if (is_array($userGroupId))
{
if (
in_array($this->user_group_id, $userGroupId)
|| array_intersect($userGroupId, $this->secondary_group_ids)
)
{
return true;
}
}
else
{
if ($this->user_group_id == $userGroupId || in_array($userGroupId, $this->secondary_group_ids))
{
return true;
}
}
return false;
}
public function isOnline()
{
if (!$this->canViewOnlineStatus())
{
return false;
}
$onlineCutOff = time() - $this->app()->options()->onlineStatusTimeout * 60;
return ($this->user_id == \XF::visitor()->user_id || ($this->Activity && $this->Activity->view_date > $onlineCutOff));
}
/**
* Determines if links posted by this user should be considered trusted. For example, this will mean that the
* links are "follow" (rather than "nofollow").
*
* @return bool
*/
public function isLinkTrusted()
{
return $this->hasPermission('general', 'bypassNofollowLinks');
}
public function authenticate($password)
{
if (!$this->Auth)
{
return false;
}
return $this->Auth->authenticate($password);
}
public function getIp($type)
{
/** @var \XF\Repository\Ip $ipRepo */
$ipRepo = $this->repository('XF:Ip');
return $ipRepo->getLoggedIp('user', $this->user_id, $type);
}
public function getSharedIpUsers($logDays)
{
/** @var \XF\Repository\Ip $ipRepo */
$ipRepo = $this->repository('XF:Ip');
return $ipRepo->getSharedIpUsers($this->user_id, $logDays);
}
public function getSpamDetails()
{
/** @var \XF\Repository\Spam $spamRepo */
$spamRepo = $this->repository('XF:Spam');
$spamTriggerLogsFinder = $spamRepo->findSpamTriggerLogs()->forContent('user', $this->user_id);
return $spamTriggerLogsFinder->fetch()->pluckNamed('details');
}
public function hasAdminPermission($permissionId)
{
if (!$this->is_admin || !$this->Admin)
{
return false;
}
/** @var \XF\Entity\Admin $admin */
$admin = $this->Admin;
return $admin->hasAdminPermission($permissionId);
}
/**
* @return int
*/
public function getLastActivity()
{
return ($this->Activity && $this->Activity->view_date ? $this->Activity->view_date : $this->last_activity_);
}
/**
* @return bool
*/
public function getIsSuperAdmin()
{
if ($this->is_admin && $this->Admin)
{
return $this->Admin->is_super_admin;
}
return false;
}
/**
* @return \XF\PermissionSet
*/
public function getPermissionSet()
{
return \XF::permissionCache()->getPermissionSet($this->permission_combination_id);
}
/**
* @return int
*/
public function getPermissionCombinationId()
{
return $this->user_state == 'valid'
? $this->getValue('permission_combination_id')
: \XF\Repository\User::$guestPermissionCombinationId;
}
/**
* @return string
*/
public function getEmailConfirmKey()
{
return hash_hmac('md5', $this->user_id . $this->email, $this->secret_key);
}
/**
* @return string
*/
public function getClientSideCacheKey(): string
{
$app = $this->app();
$style = $app->style($this->style_id);
if (!$style->isUsable($this))
{
$style = $app->style(0);
}
$language = $app->language($this->language_id);
if (!$language->isUsable($this))
{
$language = $app->language(0);
}
$interval = 86400 * 7; // 7 days
$period = \XF::$time - \XF::$time % $interval;
$cacheKey = $style->getId()
. '_' . $language->getId()
. '_' . $style->getLastModified()
. '_' . $this->user_id
. '_' . $period;
return md5($cacheKey);
}
/**
* @return int
*/
public function getWarningCount()
{
/** @var \XF\Repository\Warning $warningRepo */
$warningRepo = $this->repository('XF:Warning');
return $warningRepo->findUserWarningsForList($this->user_id)->total();
}
public function canEdit()
{
if (!$this->exists())
{
return false;
}
$visitor = \XF::visitor();
if (!$visitor->is_super_admin && $this->is_super_admin)
{
return false;
}
if ($visitor->is_admin && $visitor->hasAdminPermission('user'))
{
return true;
}
if ($this->is_admin && $this->is_moderator && $this->is_staff)
{
// moderators can't edit admins/mods/staff
return false;
}
return $visitor->hasPermission('general', 'editBasicProfile');
}
public function canBanUsers(&$error = null)
{
$visitor = \XF::visitor();
return (
$visitor->user_id &&
$visitor->is_moderator &&
$visitor->hasPermission('general', 'banUser')
);
}
public function canBan(&$error = null)
{
$visitor = \XF::visitor();
if (!$this->user_id || !$visitor->is_moderator || $this->user_id == $visitor->user_id)
{
return false;
}
if ($this->is_admin || $this->is_moderator)
{
$error = \XF::phraseDeferred('this_user_is_an_admin_or_moderator_choose_another');
return false;
}
return $visitor->hasPermission('general', 'banUser');
}
public function canCleanSpam()
{
return $this->hasPermission('general', 'cleanSpam');
}
public function isPossibleSpammer(&$error = null)
{
// guest
if (!$this->user_id)
{
return false;
}
// self
if ($this->user_id == \XF::visitor()->user_id)
{
$error = \XF::phraseDeferred('sorry_dave');
return false;
}
// staff
if ($this->is_admin || $this->is_moderator)
{
$error = \XF::phraseDeferred('spam_cleaner_no_admins_or_mods');
return false;
}
$criteria = $this->app()->options()->spamUserCriteria;
if ($criteria['message_count'] && $this->message_count > $criteria['message_count'])
{
$error = \XF::phraseDeferred('spam_cleaner_too_many_messages', ['message_count' => $criteria['message_count']]);
return false;
}
if ($criteria['register_date'] && $this->register_date < (time() - $criteria['register_date'] * 86400))
{
$error = \XF::phraseDeferred('spam_cleaner_registered_too_long', ['register_days' => $criteria['register_date']]);
return false;
}
if ($criteria['reaction_score'] && $this->reaction_score > $criteria['reaction_score'])
{
$error = \XF::phraseDeferred('spam_cleaner_reaction_score_too_high', ['reaction_score' => $criteria['reaction_score']]);
return false;
}
return true;
}
public function isSpamCheckRequired()
{
return (
!$this->is_admin
&& !$this->is_moderator
&& $this->app()->options()->maxContentSpamMessages
&& !$this->hasPermission('general', 'bypassSpamCheck')
&& $this->message_count < $this->app()->options()->maxContentSpamMessages
);
}
public function canViewOnlineStatus()
{
$visitor = \XF::visitor();
if ($this->user_state == 'disabled')
{
return false;
}
if (!$this->last_activity)
{
return false;
}
if ($this->visible || $this->user_id == $visitor->user_id)
{
return true;
}
return $visitor->canBypassUserPrivacy();
}
public function canViewCurrentActivity()
{
$visitor = \XF::visitor();
if (!$this->last_activity)
{
return false;
}
if (($this->visible && $this->activity_visible) || $this->user_id == $visitor->user_id)
{
return true;
}
return $visitor->canBypassUserPrivacy();
}
public function canViewBookmarks()
{
return $this->user_id && $this->hasPermission('bookmark', 'view');
}
public function isWarnable()
{
return !$this->is_admin && !$this->is_moderator;
}
public function canWarn(&$error = null)
{
$visitor = \XF::visitor();
if (!$visitor->user_id || $this->user_id == $visitor->user_id)
{
return false;
}
return $this->isWarnable() && $visitor->hasPermission('general', 'warn');
}
public function canViewWarnings()
{
return ($this->user_id && $this->hasPermission('general', 'viewWarning'));
}
public function canApproveRejectUser()
{
return $this->is_moderator && $this->hasPermission('general', 'approveRejectUser');
}
public function canApproveRejectUsernameChange(): bool
{
return $this->is_moderator && $this->hasPermission('general', 'approveUsernameChange');
}
public function canEditProfile()
{
return $this->exists() && $this->hasPermission('general', 'editProfile');
}
public function canEditSignature()
{
return ($this->exists()
&& $this->hasPermission('general', 'editSignature')
&& $this->hasPermission('signature', 'maxPrintable') != 0
&& $this->hasPermission('signature', 'maxLines') != 0);
}
public function canUseRte(): bool
{
if ($this->user_id)
{
return true;
}
return $this->app()->options()->allowGuestRte ?? true;
}
public function canBypassUserPrivacy()
{
return $this->exists() && $this->hasPermission('general', 'bypassUserPrivacy');
}
public function isPrivacyCheckMet($privacyKey, User $user)
{
if (!$this->Privacy)
{
return true;
}
/** @var UserPrivacy $privacy */
$privacy = $this->Privacy;
return $privacy->isPrivacyCheckMet($privacyKey, $user);
}
public function canSearch(&$error = null)
{
return $this->hasPermission('general', 'search') && $this->app()->options()->enableSearch;
}
public function canChangeLanguage(&$error = null)
{
$languages = array_filter($this->app()->container('language.cache'), function($language)
{
return ($this->is_admin || $language['user_selectable']);
});
return (bool)(count($languages) > 1);
}
public function canChangeStyle(&$error = null)
{
$styles = array_filter($this->app()->container('style.cache'), function($style)
{
return ($this->is_admin || $style['user_selectable']);
});
return (bool)(count($styles) > 1);
}
public function canViewMemberList()
{
return $this->hasPermission('general', 'viewMemberList');
}
public function canReport(&$error = null)
{
if (!$this->user_id || !$this->hasPermission('general', 'report'))
{
$error = \XF::phraseDeferred('you_may_not_report_this_content');
return false;
}
return true;
}
public function canBeReported(&$error = null)
{
if ($this->is_staff)
{
return false;
}
return \XF::visitor()->canReport($error);
}
public function canViewFullProfile(&$error = null)
{
$visitor = \XF::visitor();
if ($visitor->user_id == $this->user_id)
{
return true;
}
if (!$visitor->hasPermission('general', 'viewProfile'))
{
return false;
}
if (!$this->isPrivacyCheckMet('allow_view_profile', $visitor))
{
$error = \XF::phraseDeferred('member_limits_viewing_profile');
return false;
}
if (
($this->user_state == 'moderated' || $this->user_state == 'email_confirm' || $this->user_state == 'rejected')
&& !$visitor->canBypassUserPrivacy()
)
{
$error = \XF::phraseDeferred('this_users_profile_is_not_available');
return false;
}
if ($this->is_banned && !$visitor->canBypassUserPrivacy())
{
/** @var UserBan|null $ban */
$ban = $this->Ban;
if ($ban && !$ban->end_date)
{
$error = \XF::phraseDeferred('this_users_profile_is_not_available');
return false;
}
}
return true;
}
public function canViewBasicProfile(&$error = null)
{
return true;
}
public function canViewProfilePosts(&$error = null)
{
return $this->hasPermission('general', 'viewProfile') && $this->hasPermission('profilePost', 'view');
}
public function canViewPostsOnProfile(&$error = null)
{
return $this->canViewFullProfile($error) && \XF::visitor()->hasPermission('profilePost', 'view');
}
public function canViewDeletedPostsOnProfile()
{
return \XF::visitor()->hasPermission('profilePost', 'viewDeleted');
}
public function canViewModeratedPostsOnProfile()
{
return \XF::visitor()->hasPermission('profilePost', 'viewModerated');
}
public function canPostOnProfile()
{
$visitor = \XF::visitor();
return ($visitor->user_id
&& $visitor->hasPermission('profilePost', 'view')
&& $visitor->hasPermission('profilePost', 'post')
&& ($this->user_id == $visitor->user_id || $this->isPrivacyCheckMet('allow_post_profile', $visitor))
&& $this->user_state != 'disabled'
);
}
public function canUploadAndManageAttachmentsOnProfile(): bool
{
$visitor = \XF::visitor();
return ($visitor->user_id && $visitor->hasPermission('profilePost', 'uploadAttachment'));
}
public function canUploadVideosOnProfile(): bool
{
$options = $this->app()->options();
if (empty($options->allowVideoUploads['enabled']))
{
return false;
}
$visitor = \XF::visitor();
return ($visitor->user_id && $visitor->hasPermission('profilePost', 'uploadVideo'));
}
public function canViewLatestActivity()
{
$visitor = \XF::visitor();
if (!$this->app()->options()->enableNewsFeed)
{
return false;
}
if ($visitor->canBypassUserPrivacy())
{
return true;
}
return (
$this->isPrivacyCheckMet('allow_receive_news_feed', $visitor)
&& $this->user_state != 'disabled'
);
}
public function canViewIdentities()
{
$visitor = \XF::visitor();
if (!$visitor->hasPermission('general', 'viewProfile'))
{
return false;
}
if ($visitor->canBypassUserPrivacy())
{
return true;
}
return (
$this->isPrivacyCheckMet('allow_view_profile', $visitor)
&& $this->isPrivacyCheckMet('allow_view_identities', $visitor)
);
}
public function canViewIps()
{
return $this->exists() && $this->hasPermission('general', 'viewIps');
}
public function canFollowUser(User $user)
{
if (!$user->user_id || !$this->user_id)
{
return false;
}
if ($user->user_id == $this->user_id)
{
return false;
}
if ($this->user_state != 'valid')
{
return false;
}
if ($this->isFollowing($user))
{
return true;
}
if (!in_array($user->user_state, ['valid', 'email_confirm', 'email_confirm_edit']))
{
return false;
}
return true;
}
public function isFollowing(User $user)
{
return $this->Profile && $this->Profile->isFollowing($user);
}
public function canIgnoreUser(User $user, &$error = null)
{
if (!$user->user_id || !$this->user_id)
{
return false;
}
if ($user->is_staff)
{
$error = \XF::phraseDeferred('staff_members_may_not_be_ignored');
return false;
}
if ($user->user_id == $this->user_id)
{
$error = \XF::phraseDeferred('you_may_not_ignore_yourself');
return false;
}
if ($this->user_state != 'valid')
{
return false;
}
if (!in_array($user->user_state, ['valid', 'email_confirm', 'email_confirm_edit', 'email_bounce']))
{
return false;
}
return true;
}
public function isIgnoring($userId)
{
if (!$this->user_id)
{
return false;
}
if ($userId instanceof User)
{
$userId = $userId->user_id;
}
if (!$userId || !$this->Profile)
{
return false;
}
$ignored = $this->Profile->ignored;
return $ignored && isset($ignored[$userId]);
}
public function canStartConversation()
{
if (!$this->exists())
{
return false;
}
$maxRecipients = $this->hasPermission('conversation', 'maxRecipients');
return (
$this->hasPermission('conversation', 'start')
&& ($maxRecipients == -1 || $maxRecipients > 0)
);
}
public function canStartConversationWith(\XF\Entity\User $user)
{
if (!$this->canBypassUserPrivacy() && !$user->canReceiveConversation())
{
return false;
}
if (!$user->user_id || $user->user_id == $this->user_id)
{
return false;
}
return (
$this->canStartConversation()
&& $user->isPrivacyCheckMet('allow_send_personal_conversation', $this)
&& $user->user_state != 'disabled'
&& !$user->is_banned
);
}
public function canReceiveConversation()
{
return $this->hasPermission('conversation', 'receive');
}
public function canReceiveActivitySummaryEmail(): bool
{
return ($this->user_id
&& $this->email
&& $this->user_state == 'valid'
&& !$this->is_banned
&& $this->last_summary_email_date !== null // null == opted-out
&& $this->last_summary_email_date > 0 // 0 == paused
);
}
/**
* Checks whether the user can upload and manage attachments globally for the specified permission group.
*
* @param string $group
*
* @return bool
*/
public function canUploadAndManageAttachments($group = 'forum')
{
return ($this->user_id && $this->hasPermission($group, 'uploadAttachment'));
}
public function canUploadAvatar()
{
return ($this->user_id && $this->hasPermission('avatar', 'allowed'));
}
public function canUploadProfileBanner()
{
return ($this->user_id && $this->hasPermission('profileBanner', 'allowed'));
}
public function canUseContactForm()
{
$options = $this->app()->options();
return (
!$this->is_banned
&& $options->contactUrl['type']
&& $this->hasPermission('general', 'useContactForm')
);
}
public function canUsePushNotifications()
{
if (!\XF::isPushUsable())
{
return false;
}
return (
$this->user_id
&& $this->hasPermission('general', 'usePush')
);
}
public function canChangeEmail(&$error = null): bool
{
if (!$this->user_id)
{
return false;
}
if ($this->user_state == 'moderated')
{
return false;
}
$cutOff = \XF::$time - 3600;
$changes = $this->repository('XF:ChangeLog')->countChangeLogsSince(
'user', $this->user_id, 'email', $cutOff
);
return ($changes < 3);
}
public function canChangeUsername(&$error = null): bool
{
if (!$this->user_id)
{
return false;
}
if ($this->PendingUsernameChange)
{
$error = \XF::phrase('pending_username_awaiting_approval');
return false;
}
if ($this->hasPermission('general', 'changeUsername'))
{
$changeLimit = $this->app()->options()->usernameChangeTimeLimit;
if ($changeLimit)
{
$effectiveLastChangeDate = max($this->username_date, $this->register_date);
if ($effectiveLastChangeDate > \XF::$time - 86400 * $changeLimit)
{
$days = ceil(($this->next_allowed_username_change - \XF::$time) / 86400);
$error = \XF::phraseDeferred(
'last_username_change_was_too_recent_try_again_in_x_days',
['days' => $days]
);
return false;
}
}
return true;
}
return false;
}
public function hasViewableUsernameHistory(): bool
{
if ($this->username_date == 0)
{
// username has never been changed
return false;
}
if ($this->canViewFullUsernameHistory())
{
// if you have the ability to view this user's full history, then you're bypassing any visible/date checks
return true;
}
// otherwise, we only look for recent visible changes...
$cutOff = \XF::$time - 86400 * $this->app()->options()->usernameChangeRecentLimit;
return ($this->username_date_visible >= $cutOff);
}
public function canViewFullUsernameHistory(&$error = null): bool
{
$visitor = \XF::visitor();
if (
($visitor->user_id && $visitor->user_id == $this->user_id)
|| $visitor->canBypassUserPrivacy()
)
{
return ($this->username_date > 0);
}
else
{
return false;
}
}
public function getNextAllowedUsernameChange($lastUsernameChangeDate = null): int
{
if ($lastUsernameChangeDate === null)
{
$lastUsernameChangeDate = max($this->username_date, $this->register_date);
}
if (!$this->hasPermission('general', 'changeUsername'))
{
return 0;
}
$changeLimit = $this->app()->options()->usernameChangeTimeLimit;
if (!$changeLimit || !$lastUsernameChangeDate)
{
return 0;
}
$nextChange = $lastUsernameChangeDate + 86400 * $changeLimit;
if (\XF::$time > $nextChange)
{
return 0;
}
return $nextChange;
}
public function canCreateThread(&$error = null)
{
return $this->hasPermission('forum', 'postThread');
}
public function canCreateThreadPreReg()
{
if ($this->user_id || $this->canCreateThread())
{
return false;
}
// because we're working on "this" instead of fetching the visitor, we need to take this approach
// rather than using XF::canPerformPreRegAction
return \XF::preRegActionUser()->canCreateThread();
}
public function isShownCaptcha()
{
return !$this->user_id;
}
public function isSearchEngineIndexable(): bool
{
return true;
}
public function canTriggerPreRegAction()
{
$options = \XF::options();
return (
!$this->user_id
&& $options->preRegAction['enabled']
&& $options->registrationSetup['enabled']
);
}
public function canCompletePreRegAction()
{
$options = \XF::options();
return (
$this->user_id
&& !$this->is_banned
&& $this->Option
&& !$this->Option->is_discouraged
&& $options->preRegAction['enabled']
);
}
public function isAwaitingEmailConfirmation()
{
return in_array($this->user_state, ['email_confirm', 'email_confirm_edit']);
}
public function getAllowedUserMentions(array $mentions)
{
$maxMentions = $this->hasPermission('general', 'maxMentionedUsers');
if ($maxMentions == 0)
{
return [];
}
if ($maxMentions < 0) // unlimited
{
return $mentions;
}
return array_slice($mentions, 0, $maxMentions, true);
}
public function hasPermission($group, $permission)
{
return $this->PermissionSet->hasGlobalPermission($group, $permission);
}
public function hasContentPermission($contentType, $contentId, $permission)
{
return $this->PermissionSet->hasContentPermission($contentType, $contentId, $permission);
}
public function hasNodePermission($contentId, $permission)
{
return $this->PermissionSet->hasContentPermission('node', $contentId, $permission);
}
public function cacheNodePermissions(array $nodeIds = null)
{
if (is_array($nodeIds))
{
\XF::permissionCache()->cacheContentPermsByIds($this->permission_combination_id, 'node', $nodeIds);
}
else
{
\XF::permissionCache()->cacheAllContentPerms($this->permission_combination_id, 'node');
}
}
public function rebuildUserGroupRelations($newTransaction = true)
{
if (!$this->user_id)
{
throw new \LogicException("User must be saved first");
}
$db = $this->db();
$userId = $this->user_id;
$inserts = [];
$inserts[] = [
'user_id' => $userId,
'user_group_id' => $this->user_group_id,
'is_primary' => 1
];
foreach ($this->secondary_group_ids AS $groupId)
{
$inserts[] = [
'user_id' => $userId,
'user_group_id' => $groupId,
'is_primary' => 0
];
}
if($newTransaction)
{
$db->beginTransaction();
}
$db->delete('xf_user_group_relation', 'user_id = ?' , $this->user_id);
$db->insertBulk('xf_user_group_relation', $inserts, false, 'is_primary = VALUES(is_primary)');
if ($newTransaction)
{
$db->commit();
}
}
public function rebuildDisplayStyleGroup()
{
if (!$this->user_id)
{
throw new \LogicException("User must be saved first");
}
$groupRepo = $this->getUserGroupRepo();
$id = $groupRepo->getDisplayGroupIdForUser($this);
if ($id != $this->display_style_group_id)
{
$this->fastUpdate('display_style_group_id', $id);
}
}
public function rebuildWarningPoints()
{
if (!$this->user_id)
{
throw new \LogicException("User must be saved first");
}
/** @var \XF\Repository\Warning $warningRepo */
$warningRepo = $this->repository('XF:Warning');
$points = $warningRepo->getActiveWarningPointsForUser($this->user_id);
if ($points != $this->warning_points)
{
$this->fastUpdate('warning_points', $points);
}
}
public function rebuildPermissionCombination()
{
if (!$this->user_id)
{
throw new \LogicException("User must be saved first");
}
/** @var \XF\Repository\PermissionCombination $combinationRepo */
$combinationRepo = $this->repository('XF:PermissionCombination');
$combinationRepo->updatePermissionCombinationForUser($this);
}
public function removeUserFromGroup($groupId)
{
if ($groupId instanceof UserGroup)
{
$groupId = $groupId->user_group_id;
}
if ($this->user_group_id == $groupId)
{
$this->user_group_id = self::GROUP_REG;
return true;
}
$ids = $this->secondary_group_ids;
$position = array_search($groupId, $ids);
if ($position !== false)
{
unset($ids[$position]);
$this->secondary_group_ids = $ids;
return true;
}
return false;
}
public function toggleActivitySummaryEmail($enable)
{
if ($enable)
{
if ($this->last_summary_email_date === null)
{
$this->last_summary_email_date = \XF::$time;
}
}
else
{
$this->last_summary_email_date = null;
}
}
//************************* VERIFIERS ***************************
protected function verifyUsername(&$username)
{
if ($username === $this->getExistingValue('username'))
{
return true; // unchanged, always pass
}
/** @var \XF\Validator\Username $validator */
$validator = $this->app()->validator('Username');
$username = $validator->coerceValue($username);
if ($this->user_id)
{
$validator->setOption('self_user_id', $this->user_id);
}
if ($this->getOption('admin_edit'))
{
$validator->setOption('admin_edit', true);
}
if (!$validator->isValid($username, $errorKey))
{
$this->error($validator->getPrintableErrorValue($errorKey), 'username');
return false;
}
return true;
}
protected function verifyEmail(&$email)
{
if ($this->isUpdate() && $email === $this->getExistingValue('email'))
{
return true;
}
if ($this->getOption('admin_edit') && $email === '')
{
return true;
}
/** @var \XF\Repository\Banning $banningRepo */
$banningRepo = $this->repository('XF:Banning');
$bannedEmails = $this->app()->container('bannedEmails');
$emailValidator = $this->app()->validator('Email');
if (!$this->getOption('admin_edit'))
{
$emailValidator->setOption('banned', $bannedEmails);
}
$emailValidator->setOption('check_typos', true);
if (!$emailValidator->isValid($email, $errorKey))
{
if ($errorKey == 'banned')
{
$this->error(\XF::phrase('email_address_you_entered_has_been_banned_by_administrator'), 'email');
// try to find triggering banned email entry. try exact match first...
$emailBan = $this->_em->findOne('XF:BanEmail', ['banned_email' => $email]);
if (!$emailBan)
{
// ...otherwise find the first entry that triggered
$bannedEmail = $banningRepo->getBannedEntryFromEmail($email, $bannedEmails);
if ($bannedEmail)
{
$emailBan = $this->_em->findOne('XF:BanEmail', ['banned_email' => $bannedEmail]);
}
}
if ($emailBan)
{
$emailBan->fastUpdate('last_triggered_date', time());
}
}
else if ($errorKey == 'typo')
{
$this->error(\XF::phrase('email_address_you_entered_appears_have_typo'));
}
else
{
$this->error(\XF::phrase('please_enter_valid_email'), 'email');
}
return false;
}
$existingUser = $this->finder('XF:User')->where('email', $email)->fetchOne();
if ($existingUser && $existingUser['user_id'] != $this->user_id)
{
$this->error(\XF::phrase('email_addresses_must_be_unique'), 'email');
return false;
}
return true;
}
protected function verifyStyleId(&$styleId)
{
if ($styleId && !$this->_em->find('XF:Style', $styleId))
{
$styleId = 0;
}
return true;
}
protected function verifyLanguageId(&$languageId)
{
if ($languageId && !$this->_em->find('XF:Language', $languageId))
{
$languageId = 0;
}
return true;
}
protected function verifyTimezone(&$timezone)
{
if (!$timezone)
{
$timezone = $this->app()->options()->guestTimeZone;
}
$tzs = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC);
if (!in_array($timezone, $tzs))
{
$this->error(\XF::phrase('please_select_valid_time_zone'), 'timezone');
return false;
}
return true;
}
protected function verifyCustomTitle(&$title)
{
if ($title === $this->getExistingValue('custom_title'))
{
return true; // can always keep the existing value
}
if ($this->getOption('admin_edit'))
{
return true;
}
if ($title !== $this->app()->stringFormatter()->censorText($title))
{
$this->error(\XF::phrase('please_enter_custom_title_that_does_not_contain_any_censored_words'), 'custom_title');
return false;
}
if (!$this->is_moderator && !$this->is_admin)
{
$disallowed = $this->getOption('custom_title_disallowed');
if ($disallowed)
{
foreach ($disallowed AS $value)
{
$value = trim($value);
if ($value === '')
{
continue;
}
if (stripos($title, $value) !== false)
{
$this->error(\XF::phrase('please_enter_another_custom_title_disallowed_words'), 'custom_title');
return false;
}
}
}
}
return true;
}
//************************* LIFE CYCLE ***************************
protected function _preSave()
{
if ($this->isChanged('user_group_id') || $this->isChanged('secondary_group_ids'))
{
$groupRepo = $this->getUserGroupRepo();
$this->display_style_group_id = $groupRepo->getDisplayGroupIdForUser($this);
}
if ($this->isChanged(['user_group_id', 'secondary_group_ids']))
{
// Do not allow a primary user group also be a secondary user group.
$this->secondary_group_ids = array_diff(
$this->secondary_group_ids,
[$this->user_group_id]
);
}
if (!$this->secret_key)
{
$this->secret_key = \XF::generateRandomString(32);
}
if ($this->isInsert() && !$this->isChanged('email') && empty($this->_errors['email']))
{
$this->email = '';
}
if ($this->isChanged('email') && $this->email && empty($this->_errors['email']))
{
// Redo the duplicate email check. This tries to reduce a race condition that can be extended
// due to third-party spam checks.
$matchUserId = $this->db()->fetchOne("
SELECT user_id
FROM xf_user
WHERE email = ?
", $this->email);
if ($matchUserId && (!$this->user_id || $matchUserId != $this->user_id))
{
$this->error(\XF::phrase('email_addresses_must_be_unique'), 'username');
}
}
if ($this->isChanged('username') && empty($this->_errors['username']))
{
// Redo the duplicate name check. This tries to reduce a race condition that can be extended
// due to third-party spam checks.
$matchUserId = $this->db()->fetchOne("
SELECT user_id
FROM xf_user
WHERE username = ?
", $this->username);
if ($matchUserId && (!$this->user_id || $matchUserId != $this->user_id))
{
$this->error(\XF::phrase('usernames_must_be_unique'), 'username');
}
if ($this->isUpdate() && empty($this->_errors['username']))
{
$this->username_date = \XF::$time;
}
}
if ($this->isUpdate() && $this->isChanged('security_lock'))
{
if ($this->security_lock && $this->getOption('prevent_self_lock'))
{
$visitor = \XF::visitor();
if ($visitor->user_id == $this->user_id)
{
$this->error(\XF::phrase('you_cannot_security_lock_your_own_account'));
}
}
}
}
protected function _postSave()
{
if ($this->isChanged('user_group_id') || $this->isChanged('secondary_group_ids'))
{
$this->rebuildUserGroupRelations(false);
$this->rebuildPermissionCombination();
}
if ($this->isChanged(['user_group_id', 'secondary_group_ids', 'permission_combination_id', 'user_state']))
{
// TODO: this is mostly temporary -- this should happen at the getter level
$this->clearCache('PermissionSet');
}
if ($this->isUpdate() && $this->isChanged('username') && $this->getExistingValue('username') != null)
{
if ($this->getOption('enqueue_rename_cleanup'))
{
$this->app()->jobManager()->enqueue('XF:UserRenameCleanUp', [
'originalUserId' => $this->user_id,
'originalUserName' => $this->getExistingValue('username'),
'newUserName' => $this->username
]);
}
/** @var \XF\Repository\UsernameChange $usernameChangeRepo */
$usernameChangeRepo = $this->repository('XF:UsernameChange');
// if user has a pending username change then handle them
$usernameChangeRepo->clearPendingUsernameChanges($this);
if ($this->getOption('insert_username_change_history'))
{
$usernameChangeRepo->insertUsernameChangeLog(
$this->user_id,
$this->getExistingValue('username'),
$this->username,
$this->getOption('insert_username_change_visible')
);
}
}
$approvalChange = $this->isStateChanged('user_state', 'moderated');
if ($approvalChange == 'enter')
{
$approvalQueue = $this->getRelationOrDefault('ApprovalQueue', false);
$approvalQueue->content_type = 'user';
$approvalQueue->content_id = $this->user_id;
$approvalQueue->content_date = $this->register_date;
$approvalQueue->save();
}
else if ($approvalChange == 'leave' && $this->ApprovalQueue)
{
$this->ApprovalQueue->delete();
}
$rejectionChange = $this->isStateChanged('user_state', 'rejected');
if ($rejectionChange == 'enter' && !$this->Reject)
{
/** @var UserReject $reject */
$reject = $this->getRelationOrDefault('Reject', false);
$reject->setFromVisitor();
$reject->save();
}
else if ($rejectionChange == 'leave' && $this->Reject)
{
$this->Reject->delete();
}
if ($this->isChanged('is_staff'))
{
if ($this->isUpdate())
{
$this->repository('XF:UserIgnored')->rebuildIgnoredCacheByIgnoredUser($this->user_id);
}
$this->repository('XF:MemberStat')->emptyCache('staff_members');
}
if ($this->isUpdate() && $this->isChanged('email'))
{
// remove lost password requests when updating the email address
$this->db()->delete(
'xf_user_confirmation',
"user_id = ? AND confirmation_type = 'password'",
$this->user_id
);
}
if ($this->isUpdate() && $this->isStateChanged('security_lock', 'reset') === 'leave')
{
$this->repository('XF:UserConfirmation')->fastDeleteUserConfirmationRecords($this, 'security_lock_reset');
}
}
protected function _preDelete()
{
if (!$this->getOption('allow_self_delete'))
{
if ($this->user_id == \XF::visitor()->user_id)
{
$this->error(\XF::phrase('you_cannot_delete_your_own_account'));
}
}
}
protected function _postDelete()
{
$db = $this->db();
$userId = $this->user_id;
// This code is intentional - it is preloading these relations so that they are available throughout this
// method, even when they get directly deleted below
$this->getRelation('Auth');
$this->getRelation('Option');
$this->getRelation('Profile');
$this->getRelation('Privacy');
// Quickly delete the 1:1 entries that form the core record. We'll clean up the rest of the data in a
// separate process. Not using cascadeDelete here as we may block deletion of this stuff in the other
// entities as it could cause problems
$db->delete('xf_user_authenticate', 'user_id = ?', $userId);
$db->delete('xf_user_option', 'user_id = ?', $userId);
$db->delete('xf_user_profile', 'user_id = ?', $userId);
$db->delete('xf_user_privacy', 'user_id = ?', $userId);
/** @var \XF\Service\User\Avatar $avatar */
$avatar = $this->app()->service('XF:User\Avatar', $this);
$avatar->deleteAvatarForUserDelete();
/** @var \XF\Service\User\ProfileBanner $banner */
$banner = $this->app()->service('XF:User\ProfileBanner', $this);
$banner->deleteBannerForUserDelete();
if ($this->getOption('enqueue_delete_cleanup'))
{
$this->app()->jobManager()->enqueue('XF:UserDeleteCleanUp', [
'userId' => $this->user_id,
'username' => $this->username
]);
}
}
public function getChangeLogEntries()
{
$changes = [];
if ($this->isUpdate() && $this->isChanged('last_summary_email_date'))
{
$newValue = $this->last_summary_email_date;
$oldValue = $this->getExistingValue('last_summary_email_date');
if ($newValue === null && $oldValue !== null)
{
$changes['enable_activity_summary_email'] = [1, 0];
}
else if ($newValue !== null && $oldValue === null)
{
$changes['enable_activity_summary_email'] = [0, 1];
}
}
return $changes;
}
public function setUserRejected($reason = '', User $byUser = null)
{
if ($this->user_state == 'rejected')
{
return false;
}
$this->user_state = 'rejected';
/** @var \XF\Entity\UserReject $reject */
$reject = $this->getRelationOrDefault('Reject');
if ($byUser)
{
$reject->setFromUser($byUser);
}
$reject->reject_reason = $reason;
return true;
}
public function rejectUser($reason = '', User $byUser = null)
{
if ($this->setUserRejected($reason, $byUser))
{
$this->save();
return true;
}
return false;
}
/**
* @param \XF\Api\Result\EntityResult $result
* @param int $verbosity
* @param array $options
*
* @api-desc Information about the user. Different information will be included based on permissions and verbosity.
*
* @api-out <perm|verbose> str $about
* @api-out <perm> bool $activity_visible
* @api-out <cond> int $age The user's current age. Only included if available.
* @api-out <perm|verbose> array $alert_optout
* @api-out <perm|verbose> string $allow_post_profile
* @api-out <perm|verbose> string $allow_receive_news_feed
* @api-out <perm|verbose> string $allow_send_personal_conversation
* @api-out <perm|verbose> string $allow_view_identities
* @api-out <perm|verbose> string $allow_view_profile
* @api-out object $avatar_urls Maps from size types to URL.
* @api-out object $profile_banner_urls Maps from size types to URL.
* @api-out bool $can_ban
* @api-out bool $can_converse
* @api-out bool $can_edit
* @api-out bool $can_follow
* @api-out bool $can_ignore
* @api-out bool $can_post_profile
* @api-out bool $can_view_profile
* @api-out bool $can_view_profile_posts
* @api-out bool $can_warn
* @api-out <perm|verbose> bool $content_show_signature
* @api-out <perm|verbose> string $creation_watch_state
* @api-out <perm> object $custom_fields Map of custom field keys and values.
* @api-out <perm> string $custom_title Will have a value if a custom title has been specifically set; prefer user_title instead.
* @api-out <perm> object $dob Date of birth with year, month and day keys.
* @api-out <perm|verbose> string $email
* @api-out <perm|verbose> bool $email_on_conversation
* @api-out <perm|verbose> string $gravatar
* @api-out <perm|verbose> bool $interaction_watch_state
* @api-out <perm> bool $is_admin
* @api-out <perm> bool $is_banned
* @api-out <perm> bool $is_discouraged
* @api-out <cond> bool $is_followed True if the visitor is following this user. Only included if visitor is not a guest.
* @api-out <cond> bool $is_ignored True if the visitor is ignoring this user. Only included if visitor is not a guest.
* @api-out <perm> bool $is_moderator
* @api-out <perm> bool $is_super_admin
* @api-out <perm> int $last_activity Unix timestamp of user's last activity, if available.
* @api-out str $location
* @api-out <perm|verbose> bool $push_on_conversation
* @api-out <perm|verbose> array $push_optout
* @api-out <perm|verbose> bool $receive_admin_email
* @api-out <perm> array $secondary_group_ids
* @api-out <perm|verbose> bool $show_dob_date
* @api-out <perm|verbose> bool $show_dob_year
* @api-out str $signature
* @api-out <perm|verbose> string $timezone
* @api-out <perm|verbose> array $use_tfa
* @api-out <perm> int $user_group_id
* @api-out <perm> str $user_state
* @api-out str $user_title
* @api-out <perm> bool $visible
* @api-out <perm> int $warning_points Current warning points.
* @api-out <perm> str $website
* @api-out string $view_url
*/
protected function setupApiResultData(
\XF\Api\Result\EntityResult $result, $verbosity = self::VERBOSITY_NORMAL, array $options = []
)
{
if (!$this->user_id)
{
// possible to be called on a guest user, in which case return a stub
$result->skipColumn(['message_count', 'question_solution_count', 'register_date', 'trophy_points', 'is_staff', 'reaction_score', 'vote_score']);
return;
}
$visitor = \XF::visitor();
$isSelf = ($visitor->user_id == $this->user_id);
$isBypassingPermissions = \XF::isApiBypassingPermissions();
$hasAdminPerms = $visitor->hasAdminPermission('user');
if ($verbosity < self::VERBOSITY_NORMAL)
{
// this indicates that we just want a stub result
$includeExtendedProfile = false;
$includePrivateProfile = false;
$includeInternalProfile = false;
}
else if ($isBypassingPermissions || !empty($options['full_profile']))
{
// always return everything
$includeExtendedProfile = true;
$includePrivateProfile = true;
$includeInternalProfile = true;
}
else
{
$includeExtendedProfile = $isSelf || $this->canViewFullProfile();
$includePrivateProfile = $isSelf || $hasAdminPerms;
$includeInternalProfile = $hasAdminPerms;
}
$profile = $this->Profile;
$option = $this->Option;
$privacy = $this->Privacy;
$birthday = $profile->getBirthday($isBypassingPermissions);
// basic profile info
$result->user_title = $this->custom_title ?: $this->app()->templater()->getDefaultUserTitleForUser($this);
$result->signature = $profile->signature;
$result->location = $profile->location;
$avatarUrls = [];
foreach (array_keys($this->app()->container('avatarSizeMap')) AS $avatarSize)
{
$avatarUrls[$avatarSize] = $this->getAvatarUrl($avatarSize, null, true);
}
$result->avatar_urls = $avatarUrls;
$profileBannerUrls = [];
foreach (array_keys($this->app()->container('profileBannerSizeMap')) AS $bannerSize)
{
$profileBannerUrls[$bannerSize] = $profile->getBannerUrl($bannerSize, true);
}
$result->profile_banner_urls = $profileBannerUrls;
if (!empty($birthday['age']))
{
$result->age = $birthday['age'];
}
if ($isBypassingPermissions || $this->canViewOnlineStatus())
{
$result->last_activity = $this->last_activity;
}
if ($visitor->user_id)
{
$result->includeExtra([
'is_ignored' => $visitor->isIgnoring($this),
'is_followed' => $visitor->isFollowing($this)
]);
}
if ($isBypassingPermissions || $visitor->canViewWarnings())
{
$result->includeColumn('warning_points');
}
if ($isBypassingPermissions || $visitor->canBypassUserPrivacy())
{
$result->includeColumn('is_banned');
}
// extended profile
if ($includeExtendedProfile)
{
$result->website = $profile->website;
if ($birthday)
{
$result->dob = [
'year' => $birthday['age'] ? $profile->dob_year : null,
'month' => $profile->dob_month,
'day' => $profile->dob_day
];
}
if ($includePrivateProfile)
{
// return all values
$fieldValues = $profile->custom_fields->getFieldValues();
}
else
{
// only return what's public
$fieldValues = [];
/** @var \XF\CustomField\Definition[] $fields */
$fields = $profile->custom_fields->getDefinitionSet()->filterGroup('personal')->filter('profile');
foreach ($fields AS $fieldId => $field)
{
$fieldValues[$fieldId] = $profile->custom_fields->getFieldValue($fieldId);
}
if ($this->canViewIdentities())
{
$fields = $profile->custom_fields->getDefinitionSet()->filterGroup('contact')->filter('profile');
foreach ($fields AS $fieldId => $field)
{
$fieldValues[$fieldId] = $profile->custom_fields->getFieldValue($fieldId);
}
}
}
$result->custom_fields = (object)$fieldValues;
if ($verbosity > self::VERBOSITY_NORMAL)
{
$result->about = $profile->about;
}
}
// private profile -- things only shown to the user and to admins
if ($includePrivateProfile)
{
$result->includeColumn([
'is_admin',
'is_moderator',
'visible',
'activity_visible',
'custom_title'
]);
$result->includeGetter('is_super_admin');
if ($verbosity > self::VERBOSITY_NORMAL)
{
$result->includeColumn(['email', 'timezone', 'gravatar']);
$result->includeExtra([
'show_dob_year' => $option->show_dob_year,
'show_dob_date' => $option->show_dob_date,
'content_show_signature' => $option->content_show_signature,
'receive_admin_email' => $option->receive_admin_email,
'email_on_conversation' => $option->email_on_conversation,
'push_on_conversation' => $option->push_on_conversation,
'creation_watch_state' => $option->creation_watch_state,
'interaction_watch_state' => $option->interaction_watch_state,
'alert_optout' => $option->alert_optout,
'push_optout' => $option->push_optout,
'usa_tfa' => $option->use_tfa
]);
$result->includeExtra([
'allow_view_profile' => $privacy->allow_view_profile,
'allow_post_profile' => $privacy->allow_post_profile,
'allow_send_personal_conversation' => $privacy->allow_send_personal_conversation,
'allow_view_identities' => $privacy->allow_view_identities,
'allow_receive_news_feed' => $privacy->allow_receive_news_feed,
]);
}
}
// internal profile -- things only shown to the admin
if ($includeInternalProfile)
{
$result->includeColumn(['user_group_id', 'secondary_group_ids', 'user_state']);
$result->is_discouraged = $option->is_discouraged;
}
// general permission checks
$result->can_edit = $this->canEdit();
$result->can_ban = $this->canBan();
$result->can_warn = $this->canWarn();
$result->can_view_profile = $this->canViewFullProfile();
$result->can_view_profile_posts = $this->canViewPostsOnProfile();
$result->can_post_profile = $this->canPostOnProfile();
$result->can_follow = $visitor->canFollowUser($this);
$result->can_ignore = $visitor->canIgnoreUser($this);
$result->can_converse = $visitor->canStartConversationWith($this);
$result->view_url = $this->getContentUrl(true);
}
public function getContentUrl(bool $canonical = false, array $extraParams = [], $hash = null)
{
$route = $canonical ? 'canonical:members' : 'members';
return $this->app()->router('public')->buildLink($route, $this, $extraParams, $hash);
}
public function getContentPublicRoute()
{
return 'members';
}
public function getContentTitle(string $context = '')
{
return \XF::phrase('member_x', ['username' => $this->username]);
}
protected function _setupDefaults()
{
$options = \XF::options();
$defaults = $options->registrationDefaults;
$this->visible = $defaults['visible'] ? true : false;
$this->activity_visible = $defaults['activity_visible'] ? true : false;
$this->user_group_id = self::GROUP_REG;
$this->timezone = $options->guestTimeZone;
$this->language_id = \XF::language()->getId();
$this->last_summary_email_date = $defaults['receive_admin_email'] ? \XF::$time : null;
}
public static function getStructure(Structure $structure)
{
$structure->table = 'xf_user';
$structure->shortName = 'XF:User';
$structure->contentType = 'user';
$structure->primaryKey = 'user_id';
$structure->columns = [
'user_id' => ['type' => self::UINT, 'autoIncrement' => true, 'nullable' => true, 'changeLog' => false],
'username' => ['type' => self::STR, 'maxLength' => 50,
'required' => 'please_enter_valid_name', 'api' => true
],
'username_date' => ['type' => self::UINT, 'default' => 0, 'changeLog' => false],
'username_date_visible' => ['type' => self::UINT, 'default' => 0, 'changeLog' => false],
'email' => ['type' => self::STR, 'maxLength' => 120],
'style_id' => ['type' => self::UINT, 'default' => 0, 'changeLog' => false],
'language_id' => ['type' => self::UINT, 'default' => 0, 'changeLog' => false],
'timezone' => ['type' => self::STR, 'maxLength' => 50, 'default' => 'Europe/London'],
'visible' => ['type' => self::BOOL, 'default' => true],
'activity_visible' => ['type' => self::BOOL, 'default' => true],
'user_group_id' => ['type' => self::UINT, 'required' => true],
'secondary_group_ids' => ['type' => self::LIST_COMMA, 'default' => [],
'list' => ['type' => 'posint', 'unique' => true, 'sort' => SORT_NUMERIC]
],
'display_style_group_id' => ['type' => self::UINT, 'default' => 0, 'changeLog' => false],
'permission_combination_id' => ['type' => self::UINT, 'default' => 0, 'changeLog' => false],
'message_count' => ['type' => self::UINT, 'forced' => true, 'default' => 0, 'changeLog' => false, 'api' => true],
'question_solution_count' => ['type' => self::UINT, 'forced' => true, 'default' => 0, 'changeLog' => false, 'api' => true],
'alerts_unviewed' => ['type' => self::UINT, 'forced' => true, 'max' => 65535, 'default' => 0, 'changeLog' => false],
'alerts_unread' => ['type' => self::UINT, 'forced' => true, 'max' => 65535, 'default' => 0, 'changeLog' => false],
'conversations_unread' => ['type' => self::UINT, 'forced' => true, 'max' => 65535, 'default' => 0, 'changeLog' => false],
'register_date' => ['type' => self::UINT, 'default' => \XF::$time, 'api' => true],
'last_activity' => ['type' => self::UINT, 'default' => \XF::$time, 'changeLog' => false],
'last_summary_email_date' => ['type' => self::UINT, 'default' => null, 'nullable' => true, 'changeLog' => false],
'trophy_points' => ['type' => self::UINT, 'forced' => true, 'default' => 0, 'changeLog' => false, 'api' => true],
'avatar_date' => ['type' => self::UINT, 'default' => 0],
'avatar_width' => ['type' => self::UINT, 'max' => 65535, 'default' => 0, 'changeLog' => false],
'avatar_height' => ['type' => self::UINT, 'max' => 65535, 'default' => 0, 'changeLog' => false],
'avatar_highdpi' => ['type' => self::BOOL, 'default' => false, 'changeLog' => false],
'gravatar' => ['type' => self::STR, 'maxLength' => 120, 'default' => '',
'match' => 'email_empty'
],
'user_state' => ['type' => self::STR, 'default' => 'valid',
'allowedValues' => [
'valid', 'email_confirm', 'email_confirm_edit', 'moderated', 'email_bounce', 'rejected', 'disabled'
]
],
'security_lock' => ['type' => self::STR, 'default' => '',
'allowedValues' => [
'', 'change', 'reset'
]
],
'is_moderator' => ['type' => self::BOOL, 'default' => false],
'is_admin' => ['type' => self::BOOL, 'default' => false],
'is_staff' => ['type' => self::BOOL, 'default' => false, 'api' => true],
'is_banned' => ['type' => self::BOOL, 'default' => false],
'reaction_score' => ['type' => self::INT, 'default' => 0, 'changeLog' => false, 'api' => true],
'vote_score' => ['type' => self::INT, 'default' => 0, 'changeLog' => false, 'api' => true],
'custom_title' => ['type' => self::STR, 'maxLength' => 50, 'default' => '',
'censor' => true
],
'warning_points' => ['type' => self::UINT, 'forced' => true, 'default' => 0, 'changeLog' => false],
'secret_key' => ['type' => self::BINARY, 'maxLength' => 32, 'required' => true, 'changeLog' => false],
'privacy_policy_accepted' => ['type' => self::UINT, 'default' => 0],
'terms_accepted' => ['type' => self::UINT, 'default' => 0]
];
$structure->behaviors = [
'XF:ChangeLoggable' => [] // will pick up content type automatically
];
$structure->getters = [
'PermissionSet' => [
'getter' => true,
'cache' => true
],
'permission_combination_id' => false,
'is_super_admin' => true,
'last_activity' => true,
'email_confirm_key' => true,
'warning_count' => true,
'next_allowed_username_change' => true,
];
$structure->relations = [
'Admin' => [
'entity' => 'XF:Admin',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'Auth' => [
'entity' => 'XF:UserAuth',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'ConnectedAccounts' => [
'entity' => 'XF:UserConnectedAccount',
'type' => self::TO_MANY,
'conditions' => 'user_id',
'key' => 'provider'
],
'Option' => [
'entity' => 'XF:UserOption',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'PermissionCombination' => [
'entity' => 'XF:PermissionCombination',
'type' => self::TO_ONE,
'conditions' => 'permission_combination_id',
'proxy' => true,
'primary' => true
],
'Profile' => [
'entity' => 'XF:UserProfile',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'Privacy' => [
'entity' => 'XF:UserPrivacy',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'Ban' => [
'entity' => 'XF:UserBan',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'Reject' => [
'entity' => 'XF:UserReject',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
'Activity' => [
'entity' => 'XF:SessionActivity',
'type' => self::TO_ONE,
'conditions' => [
['user_id', '=', '$user_id'],
['unique_key', '=', '$user_id', '']
],
'primary' => true
],
'ApprovalQueue' => [
'entity' => 'XF:ApprovalQueue',
'type' => self::TO_ONE,
'conditions' => [
['content_type', '=', 'user'],
['content_id', '=', '$user_id']
],
'primary' => true
],
'Following' => [
'entity' => 'XF:UserFollow',
'type' => self::TO_MANY,
'conditions' => 'user_id',
'key' => 'follow_user_id'
],
'PendingUsernameChange' => [
'entity' => 'XF:UsernameChange',
'type' => self::TO_ONE,
'conditions' => [
['user_id', '=', '$user_id'],
['change_state', '=', 'moderated']
],
'order' => ['change_date', 'DESC']
],
'PreRegAction' => [
'entity' => 'XF:PreRegAction',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
]
];
$structure->columnAliases = [
'like_count' => 'reaction_score'
];
$options = \XF::options();
$structure->options = [
'allow_self_delete' => false,
'custom_title_disallowed' => !empty($options->disallowedCustomTitles)
? preg_split('/\r?\n/', $options->disallowedCustomTitles)
: [],
'admin_edit' => false,
'enqueue_rename_cleanup' => true,
'enqueue_delete_cleanup' => true,
'prevent_self_lock' => true,
'insert_username_change_history' => true,
'insert_username_change_visible' => false
];
$structure->withAliases = [
'api' => ['Profile', 'Privacy', 'Option', 'Activity', 'Admin']
];
return $structure;
}
/**
* @return \XF\Repository\User
*/
protected function getUserRepo()
{
return $this->repository('XF:User');
}
/**
* @return \XF\Repository\UserGroup
*/
protected function getUserGroupRepo()
{
return $this->repository('XF:UserGroup');
}
}