<?php
namespace XF\Admin\Controller;
use XF\Mvc\FormAction;
use XF\Mvc\ParameterBag;
use function count, strlen;
class User extends AbstractController
{
protected function preDispatchController($action, ParameterBag $params)
{
switch (strtolower($action))
{
case 'index':
case 'find':
break;
default:
$this->assertAdminPermission('user');
}
}
public function actionIndex()
{
return $this->plugin('XF:AdminSection')->actionView('users');
}
public function actionList()
{
$criteria = $this->filter('criteria', 'array');
$order = $this->filter('order', 'str');
$direction = $this->filter('direction', 'str');
$page = $this->filterPage();
$perPage = 20;
$showingAll = $this->filter('all', 'bool');
if ($showingAll)
{
$page = 1;
$perPage = 5000;
}
if (!$criteria)
{
$this->setSectionContext('listAllUsers');
}
else
{
$this->setSectionContext('searchForUsers');
}
$searcher = $this->searcher('XF:User', $criteria);
if ($order && !$direction)
{
$direction = $searcher->getRecommendedOrderDirection($order);
}
$searcher->setOrder($order, $direction);
$finder = $searcher->getFinder();
$finder->limitByPage($page, $perPage);
$filter = $this->filter('_xfFilter', [
'text' => 'str',
'prefix' => 'bool'
]);
if (strlen($filter['text']))
{
$finder->where('username', 'LIKE', $finder->escapeLike($filter['text'], $filter['prefix'] ? '?%' : '%?%'));
}
$total = $finder->total();
$users = $finder->fetch();
$this->assertValidPage($page, $perPage, $total, 'users/list');
if (!strlen($filter['text']) && $total == 1 && ($user = $users->first()))
{
return $this->redirect($this->buildLink('users/edit', $user));
}
$viewParams = [
'users' => $users,
'total' => $total,
'page' => $page,
'perPage' => $perPage,
'showingAll' => $showingAll,
'showAll' => (!$showingAll && $total <= 5000),
'criteria' => $searcher->getFilteredCriteria(),
'filter' => $filter['text'],
'sortOptions' => $searcher->getOrderOptions(),
'order' => $order,
'direction' => $direction
];
return $this->view('XF:User\Listing', 'user_list', $viewParams);
}
public function actionSearch()
{
$this->setSectionContext('searchForUsers');
$lastUserId = $this->filter('last_user_id', 'uint');
$lastUser = $lastUserId ? $this->em()->find('XF:User', $lastUserId) : null;
$viewParams = $this->getSearcherParams($lastUser ? ['lastUser' => $lastUser] : []);
return $this->view('XF:User\Search', 'user_search', $viewParams);
}
public function actionIpUsers()
{
/** @var \XF\Repository\Ip $ipRepo */
$ipRepo = $this->repository('XF:Ip');
$ip = $this->filter('ip', 'str');
$parsed = \XF\Util\Ip::parseIpRangeString($ip);
if (!$parsed)
{
return $this->message(\XF::phrase('please_enter_valid_ip_or_ip_range'));
}
else if ($parsed['isRange'])
{
$ips = $ipRepo->getUsersByIpRange($parsed['startRange'], $parsed['endRange']);
}
else
{
$ips = $ipRepo->getUsersByIp($parsed['startRange']);
}
if ($ips)
{
$viewParams = [
'ip' => $ip,
'ipParsed' => $parsed,
'ipPrintable' => $parsed['printable'],
'ips' => $ips
];
return $this->view('XF:User\IpUsers\Listing', 'ip_users_list', $viewParams);
}
else
{
return $this->message(\XF::phrase('no_users_logged_at_ip'));
}
}
public function actionQuickSearch()
{
$query = $this->filter('query', 'str');
if ($this->app->validator('Email')->isValid($query))
{
$this->request->set('criteria', ['email' => $query]);
return $this->rerouteController(__CLASS__, 'list');
}
else if ($ip = \XF\Util\Ip::parseIpRangeString($query))
{
$this->request->set('ip', $query);
return $this->rerouteController(__CLASS__, 'ip-users');
}
else
{
$this->request->set('criteria', ['username' => $query]);
return $this->rerouteController(__CLASS__, 'list');
}
}
public function actionFind()
{
$q = ltrim($this->filter('q', 'str', ['no-trim']));
if ($q !== '' && utf8_strlen($q) >= 2)
{
/** @var \XF\Finder\User $userFinder */
$userFinder = $this->finder('XF:User');
$users = $userFinder
->where('username', 'like', $userFinder->escapeLike($q, '?%'))
->isValidUser(true)
->fetch(10);
}
else
{
$users = [];
$q = '';
}
$viewParams = [
'q' => $q,
'users' => $users
];
return $this->view('XF:User\Find', '', $viewParams);
}
public function actionBatchUpdate()
{
$this->setSectionContext('batchUpdateUsers');
$viewParams = $this->getSearcherParams(['success' => $this->filter('success', 'bool')]);
return $this->view('XF:User\BatchUpdate', 'user_batch_update', $viewParams);
}
public function actionBatchUpdateConfirm()
{
$this->setSectionContext('batchUpdateUsers');
$this->assertPostOnly();
$criteria = $this->filter('criteria', 'array');
$searcher = $this->searcher('XF:User', $criteria);
$userIds = $this->filter('user_ids', 'array-uint');
$total = count($userIds) ?: $searcher->getFinder()->total();
if (!$total)
{
throw $this->exception($this->error(\XF::phraseDeferred('no_items_matched_your_filter')));
}
$viewParams = [
'total' => $total,
'userIds' => $userIds,
'criteria' => $searcher->getFilteredCriteria(),
'userGroups' => $this->repository('XF:UserGroup')->findUserGroupsForList()->fetch()
];
return $this->view('XF:User\BatchUpdate\Confirm', 'user_batch_update_confirm', $viewParams);
}
public function actionBatchUpdateAction()
{
$this->setSectionContext('batchUpdateUsers');
$this->assertPostOnly();
if ($this->request->exists('user_ids'))
{
$userIds = $this->filter('user_ids', 'json-array');
$total = count($userIds);
$jobCriteria = null;
}
else
{
$criteria = $this->filter('criteria', 'json-array');
$searcher = $this->searcher('XF:User', $criteria);
$total = $searcher->getFinder()->total();
$jobCriteria = $searcher->getFilteredCriteria();
$userIds = null;
}
if (!$total)
{
throw $this->exception($this->error(\XF::phraseDeferred('no_items_matched_your_filter')));
}
$actions = $this->filter('actions', 'array');
if ($this->request->exists('confirm_delete') && empty($actions['delete']))
{
return $this->error(\XF::phrase('you_must_confirm_deletion_to_proceed'));
}
$this->app->jobManager()->enqueueUnique('userAction', 'XF:UserAction', [
'total' => $total,
'actions' => $actions,
'userIds' => $userIds,
'criteria' => $jobCriteria
]);
return $this->redirect($this->buildLink('users/batch-update', null, ['success' => true]));
}
protected function userAddEdit(\XF\Entity\User $user)
{
/** @var \XF\Data\TimeZone $tzData */
$tzData = $this->data('XF:TimeZone');
/** @var \XF\Repository\Style $styleRepo */
$styleRepo = $this->repository('XF:Style');
/** @var \XF\Repository\Language $languageRepo */
$languageRepo = $this->repository('XF:Language');
$viewParams = [
'user' => $user,
'userGroups' => $this->em()->getRepository('XF:UserGroup')->getUserGroupTitlePairs(),
'timeZones' => $tzData->getTimeZoneOptions(),
'styleTree' => $styleRepo->getStyleTree(false),
'languageTree' => $languageRepo->getLanguageTree(false),
'success' => $this->filter('success', 'bool')
];
return $this->view('XF:User\Edit', 'user_edit', $viewParams);
}
public function actionEdit(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id, ['Activity', 'Option', 'Profile', 'Privacy']);
$this->assertCanEditUser($user);
return $this->userAddEdit($user);
}
public function actionAdd()
{
$this->setSectionContext('createNewUser');
$user = $this->getUserRepo()->setupBaseUser();
return $this->userAddEdit($user);
}
protected function customFieldsSaveProcess(FormAction $form, \XF\Entity\UserProfile $userProfile)
{
/** @var \XF\CustomField\Set $fieldSet */
$fieldSet = $userProfile->custom_fields;
$fieldDefinition = $fieldSet->getDefinitionSet()
->filterEditable($fieldSet, 'admin');
$customFields = $this->filter('custom_fields', 'array');
$customFieldsShown = array_keys($fieldDefinition->getFieldDefinitions());
if ($customFieldsShown)
{
$form->setup(function() use ($fieldSet, $customFields, $customFieldsShown)
{
$fieldSet->bulkSet($customFields, $customFieldsShown, 'admin', true);
});
}
}
protected function userSaveProcess(\XF\Entity\User $user)
{
$form = $this->formAction();
$input = $this->filter([
'user' => [
'username' => 'str',
'email' => 'str',
'user_group_id' => 'uint',
'secondary_group_ids' => 'array-uint',
'user_state' => 'str',
'security_lock' => 'str',
'is_staff' => 'bool',
'custom_title' => 'str',
'message_count' => 'uint',
'reaction_score' => 'int',
'trophy_points' => 'uint',
'style_id' => 'uint',
'language_id' => 'uint',
'timezone' => 'str',
'visible' => 'bool',
'activity_visible' => 'bool',
],
'option' => [
'is_discouraged' => 'bool',
'content_show_signature' => 'bool',
'email_on_conversation' => 'uint',
'creation_watch_state' => 'str',
'interaction_watch_state' => 'str',
'receive_admin_email' => 'bool',
'show_dob_date' => 'bool',
'show_dob_year' => 'bool'
],
'profile' => [
'location' => 'str',
'website' => 'str',
'about' => 'str',
'signature' => 'str'
],
'privacy' => [
'allow_view_profile' => 'str',
'allow_post_profile' => 'str',
'allow_send_personal_conversation' => 'str',
'allow_view_identities' => 'str',
'allow_receive_news_feed' => 'str',
],
'dob_day' => 'uint',
'dob_month' => 'uint',
'dob_year' => 'uint',
'change_password' => 'str',
'password' => 'str',
'disable_tfa' => 'bool',
'enable_activity_summary_email' => 'bool',
'username_change_invisible' => 'bool',
]);
$password = $this->filter('visitor_password', 'str');
if ($user->exists() && $user->is_super_admin)
{
if (!\XF::visitor()->authenticate($password))
{
throw $this->exception($this->error(\XF::phrase('your_existing_password_is_not_correct')));
}
}
$user->setOption('admin_edit', true);
$user->setOption('insert_username_change_visible', $input['username_change_invisible'] ? false : true);
$form->setup(function() use ($user, $input)
{
$user->toggleActivitySummaryEmail($input['enable_activity_summary_email']);
});
$form->basicEntitySave($user, $input['user']);
$userOptions = $user->getRelationOrDefault('Option');
$form->setupEntityInput($userOptions, $input['option']);
/** @var \XF\Entity\UserProfile $userProfile */
$userProfile = $user->getRelationOrDefault('Profile');
$userProfile->setOption('admin_edit', true);
$form->setupEntityInput($userProfile, $input['profile']);
$form->setup(function() use ($userProfile, $input)
{
$userProfile->setDob($input['dob_day'], $input['dob_month'], $input['dob_year']);
});
$this->customFieldsSaveProcess($form, $userProfile);
$userPrivacy = $user->getRelationOrDefault('Privacy');
$form->setupEntityInput($userPrivacy, $input['privacy']);
$form->validate(function(FormAction $form) use ($input, $user)
{
if (!$user->exists() && !$input['password'])
{
$form->logError(\XF::phrase('please_enter_valid_password'), 'password');
}
});
$passwordChanged = false;
/** @var \XF\Entity\UserAuth $userAuth */
$userAuth = $user->getRelationOrDefault('Auth');
if ($input['password'] && (!$user->exists() || $input['change_password'] == 'change'))
{
$form->setup(function() use ($userAuth, $input)
{
$userAuth->setPassword($input['password']);
});
$passwordChanged = true;
}
else if ($input['change_password'] == 'generate')
{
/** @var \XF\Service\User\PasswordReset $passwordReset */
$passwordReset = $this->service('XF:User\PasswordReset', $user);
$passwordReset->setAdminReset(true);
$form->setup(function(FormAction $form) use($userAuth, $user)
{
if ($user->email)
{
$userAuth->resetPassword();
}
else
{
$form->logError(\XF::phrase('cannot_generate_new_password_without_email_address'));
}
});
$form->complete(function() use ($passwordReset)
{
$passwordReset->triggerConfirmation();
});
$passwordChanged = true;
}
if ($passwordChanged && $user->exists())
{
$form->complete(function() use ($user)
{
if ($user->user_id == \XF::visitor()->user_id)
{
$this->plugin('XF:Login')->handleVisitorPasswordChange();
}
else
{
$this->repository('XF:UserRemember')->clearUserRememberRecords($user->user_id);
}
});
}
if ($user->exists() && $input['disable_tfa'])
{
/** @var \XF\Repository\Tfa $tfaRepo */
$tfaRepo = $this->repository('XF:Tfa');
$form->complete(function() use ($user, $tfaRepo)
{
$tfaRepo->disableTfaForUser($user);
});
}
return $form;
}
public function actionSave(ParameterBag $params)
{
$this->assertPostOnly();
if ($params->user_id)
{
$user = $this->assertUserExists($params->user_id);
$this->assertCanEditUser($user);
}
else
{
$user = null;
}
$user = $this->getUserRepo()->setupBaseUser($user);
$this->userSaveProcess($user)->run();
return $this->redirect($this->buildLink('users/search', null, ['last_user_id' => $user->user_id]));
}
public function actionExtra(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
/** @var \XF\Repository\UserUpgrade $upgradeRepo */
$upgradeRepo = $this->repository('XF:UserUpgrade');
$upgrades = $upgradeRepo->findActiveUserUpgradesForList()->where('user_id', $user->user_id)->fetch();
/** @var \XF\Repository\ConnectedAccount $connectedRepo */
$connectedRepo = $this->repository('XF:ConnectedAccount');
$connectedProviders = $connectedRepo->getUsableProviders();
$viewParams = [
'user' => $user,
'upgrades' => $upgrades,
'connectedProviders' => $connectedProviders
];
return $this->view('XF:User\Extra', 'user_extra', $viewParams);
}
public function actionUserIps(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
/** @var \XF\Repository\Ip $ipRepo */
$ipRepo = $this->repository('XF:Ip');
$ips = $ipRepo->getIpsByUser($user);
$viewParams = [
'user' => $user,
'ips' => $ips
];
return $this->view('XF:User\IpList', 'user_ip_list', $viewParams);
}
public function actionAvatar(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
$this->assertCanEditUser($user);
if ($this->isPost())
{
/** @var \XF\Service\User\Avatar $avatarService */
$avatarService = $this->service('XF:User\Avatar', $user);
$avatarService->logIp(false);
$upload = $this->request->getFile('upload', false, false);
if ($upload)
{
if (!$avatarService->setImageFromUpload($upload))
{
return $this->error($avatarService->getError());
}
if (!$avatarService->updateAvatar())
{
return $this->error(\XF::phrase('new_avatar_could_not_be_processed'));
}
}
else if ($this->filter('delete_avatar', 'bool'))
{
$avatarService->deleteAvatar();
}
return $this->redirect($this->buildLink('users/edit', $user, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\Avatar', 'user_avatar', $viewParams);
}
}
public function actionBanner(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
$this->assertCanEditUser($user);
if ($this->isPost())
{
/** @var \XF\Service\User\ProfileBanner $bannerService */
$bannerService = $this->service('XF:User\ProfileBanner', $user);
$bannerService->logIp(false);
$upload = $this->request->getFile('upload', false, false);
if ($upload)
{
if (!$bannerService->setImageFromUpload($upload))
{
return $this->error($bannerService->getError());
}
if (!$bannerService->updateBanner())
{
return $this->error(\XF::phrase('new_banner_could_not_be_processed'));
}
}
else if ($this->filter('delete_banner', 'bool'))
{
$bannerService->deleteBanner();
}
return $this->redirect($this->buildLink('users/edit', $user, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\Banner', 'user_banner', $viewParams);
}
}
public function actionResendConfirmation(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
return $this->plugin('XF:EmailConfirmation')->actionResend(
$user, $this->buildLink('users/resend-confirmation', $user), ['view' => 'XF:User\ResendConfirmation']
);
}
public function actionDelete(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
$this->assertCanEditUser($user);
if ($this->isPost())
{
if ($user->is_super_admin)
{
if (!\XF::visitor()->authenticate($this->filter('visitor_password', 'str')))
{
return $this->error(\XF::phrase('your_existing_password_is_not_correct'));
}
}
$redirect = $this->getDynamicRedirectIfNot(
$this->buildLink('users/edit', $user),
$this->buildLink('users/list')
);
/** @var \XF\Service\User\Delete $deleter */
$deleter = $this->service('XF:User\Delete', $user);
if ($this->filter('rename', 'bool'))
{
$renameTo = $this->filter('rename_to', 'str');
if (!$renameTo)
{
return $this->error(\XF::phrase('please_enter_name_to_rename_this_user_to'));
}
$deleter->renameTo($renameTo);
}
if (!$deleter->delete($errors))
{
return $this->error($errors);
}
return $this->redirect($redirect);
}
else
{
if (!$user->preDelete())
{
return $this->error($user->getErrors());
}
$viewParams = [
'user' => $user
];
return $this->view('XF:User\Delete', 'user_delete', $viewParams);
}
}
public function actionChangeLog(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
$page = $this->filterPage();
$perPage = 20;
$changeRepo = $this->repository('XF:ChangeLog');
$changeFinder = $changeRepo->findChangeLogsByContent('user', $user->user_id)->limitByPage($page, $perPage);
$changes = $changeFinder->fetch();
$changeRepo->addDataToLogs($changes);
$viewParams = [
'user' => $user,
'changesGrouped' => $changeRepo->groupChangeLogs($changes),
'page' => $page,
'perPage' => $perPage,
'total' => $changeFinder->total()
];
return $this->view('XF:User\ChangeLog', 'user_change_log', $viewParams);
}
public function actionMerge(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
if ($user->is_admin || $user->is_moderator)
{
return $this->noPermission();
}
if ($this->isPost())
{
$targetName = $this->filter('username', 'str');
/** @var \XF\Entity\User $target */
$target = $this->em()->findOne('XF:User', ['username' => $targetName]);
if (!$target)
{
return $this->error(\XF::phrase('requested_user_not_found'));
}
$jobId = 'userMerge' . $target->user_id . '-' . $user->user_id;
$this->app->jobManager()->enqueueUnique($jobId, 'XF:UserMerge', [
'sourceUserId' => $user->user_id,
'targetUserId' => $target->user_id
]);
return $this->redirect($this->buildLink('users/edit', $target, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\Merge', 'user_merge', $viewParams);
}
}
public function actionDeleteConversations(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
if ($user->is_admin || $user->is_moderator)
{
return $this->noPermission();
}
if ($this->isPost())
{
/** @var \XF\Repository\Conversation $convRepo */
$convRepo = $this->repository('XF:Conversation');
$db = \XF::db();
$db->beginTransaction();
$convFinder = $convRepo->findConversationsStartedByUser($user);
foreach ($convFinder->fetch() AS $conversation)
{
$conversation->delete(false, false);
}
$db->commit();
return $this->redirect($this->buildLink('users/edit', $user, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\DeleteConversations', 'user_delete_conversations', $viewParams);
}
}
public function actionRevertMessageEdit(ParameterBag $params)
{
$options = $this->options();
$user = $this->assertUserExists($params->user_id);
if ($user->is_super_admin || !$options->editHistory['enabled'])
{
return $this->noPermission();
}
if ($this->isPost())
{
$cutOff = max(0, \XF::$time - $this->filter('cutoff', 'timeoffset'));
if ($options->editHistory['length'])
{
$cutOff = max($cutOff, \XF::$time - $options->editHistory['length'] * 86400);
}
$this->app->jobManager()->enqueue('XF:UserRevertMessageEdit', [
'userId' => $user->user_id,
'cutOff' => $cutOff
], true);
return $this->redirect($this->buildLink('users/edit', $user, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\RevertMessageEdit', 'user_revert_message_edit', $viewParams);
}
}
public function actionRemoveReactions(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
$this->assertCanEditUser($user);
if ($this->isPost())
{
$cutOff = max(0, \XF::$time - $this->filter('cutoff', 'timeoffset'));
$this->app->jobManager()->enqueue('XF:UserRemoveReactions', [
'userId' => $user->user_id,
'cutOff' => $cutOff
], true);
return $this->redirect($this->buildLink('users/edit', $user, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\RemoveReactions', 'user_remove_reactions', $viewParams);
}
}
public function actionManageWatchedThreads(ParameterBag $params)
{
$user = $this->assertUserExists($params->user_id);
$this->assertCanEditUser($user);
if ($this->isPost())
{
/** @var \XF\Repository\ThreadWatch $threadWatchRepo */
$threadWatchRepo = $this->repository('XF:ThreadWatch');
$action = $this->filter('action', 'str');
if ($threadWatchRepo->isValidWatchState($action))
{
$threadWatchRepo->setWatchStateForAll($user, $action);
}
return $this->redirect($this->buildLink('users/edit', $user, ['success' => true]));
}
else
{
$viewParams = [
'user' => $user
];
return $this->view('XF:User\ManageWatchedThreads', 'user_manage_watched_threads', $viewParams);
}
}
protected function prepareAlertData()
{
$alert = $this->filter([
'from_user' => 'str',
'link_url' => 'str',
'link_title' => 'str',
'alert_body' => 'str'
]);
$user = null;
if ($alert['from_user'])
{
$user = $this->finder('XF:User')->where('username', $alert['from_user'])->fetchOne();
if (!$user)
{
throw $this->exception($this->error(\XF::phraseDeferred('requested_user_x_not_found', ['name' => $alert['from_user']])));
}
}
$alert['username'] = $user ? $user->username : '';
$alert['user_id'] = $user ? $user->user_id : 0;
if (!$alert['alert_body'] && !$alert['link_url'])
{
throw $this->exception($this->error(\XF::phraseDeferred('please_complete_required_fields')));
}
$data = $this->plugin('XF:UserCriteriaAction')->getInitializedSearchData();
$data['alert'] = $alert;
$data['user'] = $user;
return $data;
}
public function actionAlert()
{
$this->setSectionContext('alertUsers');
return $this->view('XF:User\Alert', 'user_alert', $this->getSearcherParams([
'sent' => $this->filter('sent', 'uint')
]));
}
public function actionAlertConfirm()
{
$this->setSectionContext('alertUsers');
$this->assertPostOnly();
$data = $this->prepareAlertData();
$viewParams = [
'alert' => $data['alert'],
'user' => $data['user'],
'total' => $data['total'],
'criteria' => $data['criteria']
];
return $this->view('XF:User\AlertConfirm', 'user_alert_confirm', $viewParams);
}
public function actionAlertSend()
{
$this->setSectionContext('alertUsers');
$this->assertPostOnly();
$data = $this->prepareAlertData();
if ($this->filter('test', 'bool'))
{
$this->app->job('XF:UserAlert', null, [
'userIds' => [\XF::visitor()->user_id],
'alert' => $data['alert']
])->run(0);
return $this->rerouteController(__CLASS__, 'alertConfirm');
}
$this->app->jobManager()->enqueueUnique('userAlertSend', 'XF:UserAlert', [
'criteria' => $data['criteria'],
'alert' => $data['alert']
]);
return $this->redirect($this->buildLink(
'users/alert', null, ['sent' => $data['total']]
));
}
protected function prepareEmailData()
{
$email = $this->filter([
'list_only' => 'bool',
'from_name' => 'str',
'from_email' => 'str',
'email_title' => 'str',
'email_format' => 'str',
'email_body' => 'str',
'email_wrapped' => 'bool',
'email_unsub' => 'bool'
]);
if (!$email['list_only'] && (!$email['from_name'] || !$email['from_email'] || !$email['email_title'] || !$email['email_body']))
{
throw $this->exception($this->error(\XF::phraseDeferred('please_complete_required_fields')));
}
if (strpos($email['email_body'], '{unsub}') !== false)
{
$email['email_unsub'] = false;
}
$data = $this->plugin('XF:UserCriteriaAction')->getInitializedSearchData([
'no_empty_email' => true
]);
$data['email'] = $email;
return $data;
}
public function actionEmail()
{
$this->setSectionContext('emailUsers');
$viewParams = $this->getSearcherParams([
'sent' => $this->filter('sent', 'uint')
]);
$viewParams['criteria']['user_state'] = ['valid'];
$viewParams['criteria']['is_banned'] = [0];
return $this->view('XF:User\Email', 'user_email', $viewParams);
}
public function actionEmailConfirm()
{
$this->setSectionContext('emailUsers');
$this->assertPostOnly();
$data = $this->prepareEmailData();
if ($this->filter('list_only', 'bool'))
{
/** @var \XF\Searcher\AbstractSearcher $searcher */
$searcher = $data['searcher'];
return $this->view('XF:User\EmailList', 'user_email_list', [
'users' => $searcher->getFinder()->fetch()
]);
}
$viewParams = [
'email' => $data['email'],
'total' => $data['total'],
'criteria' => $data['criteria'],
'tested' => $this->filter('tested', 'bool')
];
return $this->view('XF:User\EmailConfirm', 'user_email_confirm', $viewParams);
}
public function actionEmailSend()
{
$this->setSectionContext('emailUsers');
$this->assertPostOnly();
$data = $this->prepareEmailData();
if ($this->filter('test', 'bool'))
{
$this->app->job('XF:UserEmail', null, [
'userIds' => [\XF::visitor()->user_id],
'email' => $data['email']
])->run(0);
$this->request->set('tested', '1');
return $this->rerouteController(__CLASS__, 'emailConfirm');
}
$this->app->jobManager()->enqueueUnique('userEmailSend', 'XF:UserEmail', [
'criteria' => $data['criteria'],
'email' => $data['email']
]);
return $this->redirect($this->buildLink(
'users/email', null, ['sent' => $data['total']]
));
}
protected function prepareMessageData()
{
$message = $this->filter([
'from_user' => 'str',
'message_title' => 'str',
'message_body' => 'str',
'open_invite' => 'bool',
'conversation_locked' => 'bool',
'delete_type' => 'str'
]);
$user = null;
if ($message['from_user'])
{
$user = $this->finder('XF:User')->where('username', $message['from_user'])->fetchOne();
if (!$user)
{
throw $this->exception($this->error(\XF::phraseDeferred('requested_user_x_not_found', ['name' => $message['from_user']])));
}
}
$message['username'] = $user ? $user->username : '';
$message['user_id'] = $user ? $user->user_id : 0;
if (!$message['message_title'] && !$message['message_body'])
{
throw $this->exception($this->error(\XF::phraseDeferred('please_complete_required_fields')));
}
$data = $this->plugin('XF:UserCriteriaAction')->getInitializedSearchData([
'not_user_id' => $user->user_id
]);
$data['message'] = $message;
$data['user'] = $user;
return $data;
}
public function actionMessage()
{
$this->setSectionContext('messageUsers');
return $this->view('XF:User\Message', 'user_message', $this->getSearcherParams([
'sent' => $this->filter('sent', 'uint')
]));
}
public function actionMessageConfirm()
{
$this->setSectionContext('messageUsers');
$this->assertPostOnly();
$data = $this->prepareMessageData();
$viewParams = [
'message' => $data['message'],
'user' => $data['user'],
'total' => $data['total'],
'criteria' => $data['criteria']
];
return $this->view('XF:User\MessageConfirm', 'user_message_confirm', $viewParams);
}
public function actionMessagePreview()
{
$this->setSectionContext('messageUsers');
$this->assertPostOnly();
$message = $this->filter([
'message_title' => 'str',
'message_body' => 'str'
]);
$stringFormatter = $this->app->stringFormatter();
$visitor = \XF::visitor();
$tokens = [
'{name}' => $visitor->username,
'{id}' => $visitor->user_id,
'{email}' => $visitor->email
];
$title = strtr($stringFormatter->replacePhrasePlaceholders($message['message_title']), $tokens);
$body = strtr($stringFormatter->replacePhrasePlaceholders($message['message_body']), $tokens);
$viewParams = [
'title' => $title,
'content' => $body
];
return $this->view('XF:User\MessagePreview', 'user_message_preview', $viewParams);
}
public function actionMessageSend()
{
$this->setSectionContext('messageUsers');
$this->assertPostOnly();
$data = $this->prepareMessageData();
$this->app->jobManager()->enqueueUnique('userMessageSend', 'XF:UserMessage', [
'criteria' => $data['criteria'],
'message' => $data['message']
]);
return $this->redirect($this->buildLink(
'users/message', null, ['sent' => $data['total']]
));
}
protected function getSearcherParams(array $extraParams = [])
{
$searcher = $this->searcher('XF:User');
$viewParams = [
'criteria' => $searcher->getFormCriteria(),
'sortOrders' => $searcher->getOrderOptions()
];
return $viewParams + $searcher->getFormData() + $extraParams;
}
/**
* @param string $id
* @param array|string|null $with
* @param null|string $phraseKey
*
* @return \XF\Entity\User
*/
protected function assertUserExists($id, $with = null, $phraseKey = null)
{
return $this->assertRecordExists('XF:User', $id, $with, $phraseKey);
}
protected function assertCanEditUser(\XF\Entity\User $user)
{
if ($user->is_super_admin && !\XF::visitor()->is_super_admin)
{
throw $this->exception(
$this->error(\XF::phrase('you_must_be_super_administrator_to_edit_user'))
);
}
}
/**
* @return \XF\Repository\User
*/
protected function getUserRepo()
{
return $this->repository('XF:User');
}
}