Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Pub/Controller/Account.php
<?php

namespace XF\Pub\Controller;

use
XF\Mvc\Entity\ArrayCollection;
use
XF\Mvc\ParameterBag;
use
XF\Mvc\FormAction;
use
XF\Mvc\Reply\View;

use function
boolval, count, is_array, strlen;

class
Account extends AbstractController
{
    protected function
preDispatchController($action, ParameterBag $params)
    {
       
$this->assertRegistrationRequired();
    }

    public function
actionIndex()
    {
        return
$this->rerouteController(__CLASS__, 'account-details');
    }

    protected function
addAccountWrapperParams(View $view, $selected)
    {
       
$view->setParam('pageSelected', $selected);
        return
$view;
    }

    public function
actionAccountDetails()
    {
       
$visitor = \XF::visitor();

        if (
$this->isPost())
        {
            if (
$visitor->canEditProfile())
            {
               
$this->accountDetailsSaveProcess($visitor)->run();
            }

            return
$this->redirect($this->buildLink('account/account-details'));
        }
        else
        {
           
$viewParams = [
               
'pendingUsernameChange' => $visitor->PendingUsernameChange,
               
'canChangeEmail' => $visitor->canChangeEmail()
            ];
           
$view = $this->view('XF:Account\AccountDetails', 'account_details', $viewParams);
            return
$this->addAccountWrapperParams($view, 'account_details');
        }
    }

    protected function
accountDetailsSaveProcess(\XF\Entity\User $visitor)
    {
       
$form = $this->formAction();

       
$input = $this->filter([
           
'option' => [
               
'receive_admin_email' => 'bool',
               
'show_dob_year' => 'bool',
               
'show_dob_date' => 'bool',
            ],
           
'profile' => [
               
'location' => 'str',
               
'website' => 'str'
           
],
           
'user' => [
               
'custom_title' => 'str'
           
],
           
'dob_day' => 'uint',
           
'dob_month' => 'uint',
           
'dob_year' => 'uint',
           
'enable_activity_summary_email' => 'bool',
        ]);

        if (!
$visitor->hasPermission('general', 'editCustomTitle'))
        {
            unset(
$input['user']['custom_title']);
        }

       
$input['profile']['about'] = $this->plugin('XF:Editor')->fromInput('about');

       
$form->setup(function() use ($visitor, $input)
        {
           
$visitor->toggleActivitySummaryEmail($input['enable_activity_summary_email']);
        });
       
$form->basicEntitySave($visitor, $input['user']);

       
$userOptions = $visitor->getRelationOrDefault('Option');
       
$form->setupEntityInput($userOptions, $input['option']);

       
/** @var \XF\Entity\UserProfile $userProfile */
       
$userProfile = $visitor->getRelationOrDefault('Profile');
       
$form->setup(function() use ($userProfile, $input)
        {
            if (!
$userProfile['dob_day'] || !$userProfile['dob_month'] || !$userProfile['dob_year'])
            {
               
$userProfile->setDob($input['dob_day'], $input['dob_month'], $input['dob_year']);
            }
        });
       
$this->customFieldsSaveProcess($form, 'personal', $userProfile);
       
$this->customFieldsSaveProcess($form, 'contact', $userProfile);
       
$form->setupEntityInput($userProfile, $input['profile']);

       
$form->validate(function(FormAction $form) use($input, $visitor)
        {
            if (
$input['profile']['about'] && $visitor->isSpamCheckRequired())
            {
               
$checker = $this->app()->spam()->contentChecker();
               
$checker->check($visitor, $input['profile']['about'], [
                   
'content_type' => 'user'
               
]);

               
$decision = $checker->getFinalDecision();
                switch (
$decision)
                {
                    case
'moderated':
                    case
'denied':
                       
$checker->logSpamTrigger('user_about', $visitor->user_id);
                       
$form->logError(\XF::phrase('your_content_cannot_be_submitted_try_later'));
                        break;
                }
            }
        });

       
$form->complete(function() use ($visitor)
        {
           
/** @var \XF\Repository\IP $ipRepo */
           
$ipRepo = $this->repository('XF:Ip');
           
$ipRepo->logIp($visitor->user_id, $this->request->getIp(), 'user', $visitor->user_id, 'account_details_edit');
        });

        return
$form;
    }

    protected function
setupUsernameChange() : \XF\Service\User\UsernameChange
   
{
       
/** @var \XF\Service\User\UsernameChange $service */
       
$service = $this->service('XF:User\UsernameChange', \XF::visitor());

       
$service->setNewUsername($this->filter('username', 'str'));

       
$reason = $this->filter('change_reason', 'str');
        if (
$this->options()->usernameChangeRequireReason && !strlen($reason))
        {
            throw
$this->exception($this->error(\XF::phrase('please_provide_reason_for_this_username_change')));
        }
       
$service->setChangeReason($reason);

        return
$service;
    }

    public function
actionUsername()
    {
       
$visitor = \XF::visitor();

        if (!
$visitor->canChangeUsername($error))
        {
            return
$this->noPermission($error);
        }

        if (
$this->isPost())
        {
           
$changer = $this->setupUsernameChange();

            if (!
$changer->validate($errors))
            {
                return
$this->error($errors);
            }

           
/** @var \XF\Entity\UsernameChange $usernameChange */
           
$usernameChange = $changer->save();

            if (
$usernameChange->change_state == 'approved')
            {
                return
$this->redirect(
                   
$this->buildLink('account/account-details'),
                    \
XF::phrase('your_username_has_been_changed_successfully')
                );
            }
            else
            {
                return
$this->redirect(
                   
$this->buildLink('account/account-details'),
                    \
XF::phrase('your_username_change_must_be_approved_by_moderator')
                );
            }
        }
        else
        {
           
$view = $this->view('XF:Account\Username', 'account_username');
            return
$this->addAccountWrapperParams($view, 'account_details');
        }
    }

    public function
actionEmail()
    {
       
$visitor = \XF::visitor();
       
$auth = $visitor->Auth->getAuthenticationHandler();
        if (!
$auth)
        {
            return
$this->noPermission();
        }

        if (!
$visitor->canChangeEmail($error))
        {
            if (!
$error)
            {
               
$error = \XF::phrase('your_email_may_not_be_changed_at_this_time');
            }
            return
$this->error($error);
        }

        if (
$this->isPost())
        {
           
$this->emailSaveProcess($visitor)->run();

            return
$this->redirect($this->buildLink('account/account-details'));
        }
        else
        {
           
$viewParams = [
               
'hasPassword' => $auth->hasPassword()
            ];
           
$view = $this->view('XF:Account\Email', 'account_email', $viewParams);

            return
$this->addAccountWrapperParams($view, 'account_details');
        }
    }

    protected function
emailSaveProcess(\XF\Entity\User $visitor)
    {
       
$form = $this->formAction();

       
$input = $this->filter([
           
'email' => 'str',
           
'password' => 'str'
       
]);

        if (
$input['email'] != $visitor->email || $visitor->user_state === 'email_bounce')
        {
           
/** @var \XF\Service\User\EmailChange $emailChange */
           
$emailChange = $this->service('XF:User\EmailChange', $visitor, $input['email']);

           
$form->validate(function(FormAction $form) use ($visitor, $input, $emailChange)
            {
                if (!
$visitor->authenticate($input['password']))
                {
                   
$form->logError(\XF::phrase('your_existing_password_is_not_correct'), 'visitor_password');
                }
                else if (!
$emailChange->isValid($changeError))
                {
                   
$form->logError($changeError, 'email');
                }
                else if (!
$emailChange->canChangeEmail($error))
                {
                    if (!
$error)
                    {
                       
$error = \XF::phrase('your_email_may_not_be_changed_at_this_time');
                    }
                   
$form->logError($error, 'email');
                }
            });
           
$form->apply(function() use ($emailChange)
            {
               
$emailChange->save();
            });
        }

        return
$form;
    }

    public function
actionSignature()
    {
       
$visitor = \XF::visitor();
        if (!
$visitor->canEditSignature())
        {
            return
$this->noPermission();
        }

       
$sigEditor = $this->service('XF:User\SignatureEdit', $visitor);

        if (
$this->isPost())
        {
           
$signature = $this->plugin('XF:Editor')->fromInput('signature');
           
$this->signatureSaveProcess($sigEditor, $signature)->run();

            return
$this->redirect($this->buildLink('account/signature'));
        }
        else
        {
           
$viewParams = [
               
'disabledButtons' => $sigEditor->getDisabledEditorButtons()
            ];
           
$view = $this->view('XF:Account\Signature', 'account_signature', $viewParams);
            return
$this->addAccountWrapperParams($view, 'signature');
        }
    }

    protected function
signatureSaveProcess(\XF\Service\User\SignatureEdit $sigEditor, $inputSignature)
    {
       
$form = $this->formAction();

       
$form->validate(function(FormAction $form) use ($sigEditor, $inputSignature)
        {
            if (!
$sigEditor->setSignature($inputSignature, $errors))
            {
               
$form->logErrors($errors);
            }
        });
       
$form->apply(function() use ($sigEditor)
        {
           
$sigEditor->save();
        });

       
$visitor = \XF::visitor();
       
$form->complete(function() use ($visitor)
        {
           
/** @var \XF\Repository\IP $ipRepo */
           
$ipRepo = $this->repository('XF:Ip');
           
$ipRepo->logIp($visitor->user_id, $this->request->getIp(), 'user', $visitor->user_id, 'signature_edit');
        });

        return
$form;
    }

    public function
actionPrivacy()
    {
        if (
$this->isPost())
        {
           
$this->savePrivacyProcess(\XF::visitor())->run();
            return
$this->redirect($this->buildLink('account/privacy'));
        }
        else
        {
           
$view = $this->view('XF:Account\Privacy', 'account_privacy');
            return
$this->addAccountWrapperParams($view, 'privacy');
        }
    }

    protected function
savePrivacyProcess(\XF\Entity\User $visitor)
    {
       
$form = $this->formAction();

       
$input = $this->filter([
           
'user' => [
               
'visible' => 'bool',
               
'activity_visible' => 'bool',
            ],
           
'option' => [
               
'receive_admin_email' => 'bool',
               
'show_dob_date' => 'bool',
               
'show_dob_year' => 'bool'
           
],
           
'privacy' => [
               
'allow_view_profile' => 'str',
               
'allow_post_profile' => 'str',
               
'allow_receive_news_feed' => 'str',
               
'allow_send_personal_conversation' => 'str',
               
'allow_view_identities' => 'str'
           
],
           
'enable_activity_summary_email' => 'bool',
        ]);

       
$form->setup(function() use ($visitor, $input)
        {
           
$visitor->toggleActivitySummaryEmail($input['enable_activity_summary_email']);
        });
       
$form->basicEntitySave($visitor, $input['user']);

       
$userOptions = $visitor->getRelationOrDefault('Option');
       
$form->setupEntityInput($userOptions, $input['option']);

       
$userPrivacy = $visitor->getRelationOrDefault('Privacy');
       
$form->setupEntityInput($userPrivacy, $input['privacy']);

       
$form->complete(function() use ($visitor)
        {
           
/** @var \XF\Repository\IP $ipRepo */
           
$ipRepo = $this->repository('XF:Ip');
           
$ipRepo->logIp($visitor->user_id, $this->request->getIp(), 'user', $visitor->user_id, 'privacy_edit');
        });

        return
$form;
    }

    public function
actionPreferences()
    {
        if (
$this->isPost())
        {
           
$this->preferencesSaveProcess(\XF::visitor())->run();
            return
$this->redirect($this->buildLink('account/preferences'));
        }
        else
        {
           
$styles = $this->repository('XF:Style')->getUserSelectableStyles();

           
/** @var \XF\Repository\Language $languageRepo */
           
$languageRepo = $this->repository('XF:Language');

           
/** @var \XF\Data\TimeZone $tzData */
           
$tzData = $this->data('XF:TimeZone');

           
$alertRepo = $this->repository('XF:UserAlert');

           
$viewParams = [
               
'styles' => $styles,
               
'defaultStyle' => $styles[$this->app->options()->defaultStyleId] ?? [],

               
'languages' => $languageRepo->getUserSelectableLanguages(),

               
'timeZones' => $tzData->getTimeZoneOptions(),

               
'alertOptOuts' => $alertRepo->getAlertOptOuts()
            ];

           
$view = $this->view('XF:Account\Preferences', 'account_preferences', $viewParams);
            return
$this->addAccountWrapperParams($view, 'preferences');
        }
    }

    protected function
preferencesSaveProcess(\XF\Entity\User $visitor)
    {
       
$form = $this->formAction();

       
$input = $this->filter([
           
'user' => [
               
'style_id' => 'uint',
               
'language_id' => 'uint',
               
'timezone' => 'str',
               
'visible' => 'bool',
               
'activity_visible' => 'bool',
            ],
           
'option' => [
               
'creation_watch_state' => 'str',
               
'interaction_watch_state' => 'str',
               
'content_show_signature' => 'bool',
               
'receive_admin_email' => 'bool',
               
'email_on_conversation' => 'bool',
               
'push_on_conversation' => 'bool'
           
],
           
'restore_notices' => 'bool',
           
'enable_activity_summary_email' => 'bool',
        ]);

       
$alertRepo = $this->repository('XF:UserAlert');
       
$optOutActions = $alertRepo->getAlertOptOutActions();
       
$alert = $this->filter('alert', 'array-bool');
       
$push = $this->filter('push', 'array-bool');
       
$pushShown = $this->filter('push_shown', 'array-bool');

       
$alertOptOuts = [];
       
$pushOptOuts = [];
        foreach (
array_keys($optOutActions) AS $optOut)
        {
            if (empty(
$alert[$optOut]))
            {
               
$alertOptOuts[$optOut] = $optOut;
            }
            if (empty(
$push[$optOut]) && isset($pushShown[$optOut]))
            {
               
$pushOptOuts[$optOut] = $optOut;
            }
        }

       
$input['option']['alert_optout'] = $alertOptOuts;

        if (
$visitor->canUsePushNotifications())
        {
           
$input['option']['push_optout'] = $pushOptOuts;
        }

       
$form->setup(function() use ($visitor, $input)
        {
           
$visitor->toggleActivitySummaryEmail($input['enable_activity_summary_email']);
        });
       
$form->basicEntitySave($visitor, $input['user']);

       
$userOptions = $visitor->getRelationOrDefault('Option');
       
$form->setupEntityInput($userOptions, $input['option']);

       
$this->customFieldsSaveProcess($form, 'preferences');

       
$form->apply(function() use($input, $visitor)
        {
            if (
$input['restore_notices'])
            {
               
$this->repository('XF:Notice')->restoreDismissedNotices($visitor);
               
$this->session()->remove('dismissedNotices'); // force recache
           
}
        });
       
$form->complete(function() use ($visitor)
        {
           
/** @var \XF\Repository\IP $ipRepo */
           
$ipRepo = $this->repository('XF:Ip');
           
$ipRepo->logIp($visitor->user_id, $this->request->getIp(), 'user', $visitor->user_id, 'preferences_edit');
        });

        return
$form;
    }

    public function
actionDismissNotice()
    {
       
/** @var \XF\Entity\Notice $notice */
       
$notice = $this->assertRecordExists('XF:Notice', $this->filter('notice_id', 'uint'));

        if (!
$notice->canDismissNotice($error))
        {
            return
$this->error($error);
        }

        if (
$this->isPost())
        {
           
$this->repository('XF:Notice')->dismissNotice($notice, \XF::visitor());
           
$this->session()->remove('dismissedNotices'); // force recache
           
return $this->redirect($this->getDynamicRedirect());
        }
        else
        {
            return
$this->view('XF:Account\DismissNotice', 'notice_dismiss', ['notice' => $notice]);
        }
    }

    public function
actionAvatar()
    {
       
$visitor = \XF::visitor();
        if (!
$visitor->canUploadAvatar())
        {
            return
$this->noPermission();
        }

        if (
$this->isPost())
        {
           
$useCustom = $this->filter('use_custom', 'bool');
           
$message = '';

           
/** @var \XF\Service\User\Avatar $avatarService */
           
$avatarService = $this->service('XF:User\Avatar', $visitor);

            if (
$this->filter('delete_avatar', 'bool'))
            {
               
$avatarService->deleteAvatar();
            }
            else if (
$useCustom)
            {
               
$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 (
$visitor->avatar_date)
                {
                   
// recrop existing avatar
                   
$cropX = $this->filter('avatar_crop_x', 'uint');
                   
$cropY = $this->filter('avatar_crop_y', 'uint');
                    if (
$cropX != $visitor->Profile->avatar_crop_x || $cropY != $visitor->Profile->avatar_crop_y)
                    {
                       
$avatarService->setImageFromExisting();
                       
$avatarService->setCrop($cropX, $cropY);
                        if (!
$avatarService->updateAvatar())
                        {
                            return
$this->error(\XF::phrase('new_avatar_could_not_be_processed'));
                        }
                    }
                    else
                    {
                       
$avatarService->removeGravatar();
                    }
                }
            }
            else if (
$this->options()->gravatarEnable)
            {
               
$gravatar = $this->filter('gravatar', 'str');
                if (
$this->filter('test_gravatar', 'str'))
                {
                   
$gravatarValidator = $this->app->validator('Gravatar');
                    if (!
$gravatarValidator->isValid($gravatar, $errorKey))
                    {
                        return
$this->error($gravatarValidator->getPrintableErrorValue($errorKey));
                    }


                   
$reply = $this->view('XF:Account\Avatar');
                   
$reply->setJsonParams([
                       
'gravatarTest' => $gravatar,
                       
'gravatarPreview' => $visitor->getGravatarUrl('m', $gravatar)
                    ]);
                    return
$reply;
                }

                if (!
$avatarService->setGravatar($gravatar))
                {
                    return
$this->error($avatarService->getError());
                }
            }

            if (
$this->filter('_xfWithData', 'bool'))
            {
                return
$this->view('XF:Account\AvatarUpdate', '');
            }
            else
            {
                return
$this->redirect($this->buildLink('account/avatar'));
            }
        }
        else
        {
           
$viewParams = [
               
'maxSize' => $this->app->container('avatarSizeMap')['m'],
               
'maxDimension' => ($visitor->avatar_width > $visitor->avatar_height ? 'height' : 'width'),
               
'x' => $visitor->Profile->avatar_crop_x,
               
'y' => $visitor->Profile->avatar_crop_y
           
];
           
$view = $this->view('XF:Account\Avatar', 'account_avatar', $viewParams);
            return
$this->addAccountWrapperParams($view, 'account_details');
        }
    }

    public function
actionBanner()
    {
       
$visitor = \XF::visitor();
        if (!
$visitor->canUploadProfileBanner())
        {
            return
$this->noPermission();
        }

        if (
$this->isPost())
        {
           
/** @var \XF\Service\User\ProfileBanner $bannerService */
           
$bannerService = $this->service('XF:User\ProfileBanner', $visitor);

            if (
$this->filter('delete_banner', 'bool'))
            {
               
$bannerService->deleteBanner();
            }
            else
            {
               
$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 (
$visitor->Profile->banner_date)
                {
                   
// reposition existing avatar
                   
$posY = $this->filter('banner_position_y', 'uint');
                    if (
$posY != $visitor->Profile->banner_position_y)
                    {
                        if (!
$bannerService->setPosition($posY))
                        {
                            return
$this->error(\XF::phrase('new_banner_could_not_be_processed'));
                        }
                    }
                }
            }

            if (
$this->filter('_xfWithData', 'bool'))
            {
                return
$this->view('XF:Account\BannerUpdate', '');
            }
            else
            {
                return
$this->redirect($this->buildLink('account/banner'));
            }
        }
        else
        {
           
$viewParams = [];
           
$view = $this->view('XF:Account\Banner', 'account_banner', $viewParams);
            return
$this->addAccountWrapperParams($view, 'account_details');
        }
    }

    public function
actionFollowing()
    {
       
$followingUsers = [];
       
$visitor = \XF::visitor();
        if (
$following = $visitor->Profile->following)
        {
           
$followingUsers = $this->finder('XF:User')
                ->
where('user_id', $following)
                ->
order('username')
                ->
fetch();
        }

       
$viewParams = [
           
'following' => $followingUsers
       
];
       
$view = $this->view('XF:Account\Following', 'account_following', $viewParams);
        return
$this->addAccountWrapperParams($view, 'following');
    }

    public function
actionIgnored()
    {
       
$visitor = \XF::visitor();
        if (
$ignored = $visitor->Profile->ignored)
        {
           
$ignoringUsers = $this->finder('XF:User')
                ->
where('user_id', array_keys($ignored))
                ->
order('username')
                ->
fetch();
        }
        else
        {
           
$ignoringUsers = $this->em()->getEmptyCollection();
        }

       
$viewParams = [
           
'ignoring' => $ignoringUsers
       
];
       
$view = $this->view('XF:Account\Ignored', 'account_ignored', $viewParams);
        return
$this->addAccountWrapperParams($view, 'ignored');
    }

    public function
actionReactions()
    {
       
$this->assertNotEmbeddedImageRequest();

       
$visitor = \XF::visitor();

       
/** @var \XF\Repository\Reaction $reactionRepo */
       
$reactionRepo = $this->repository('XF:Reaction');

       
$page = $this->filterPage();
       
$perPage = 20;

       
$typeTotals = $reactionRepo->getUserReactionsTabSummary($visitor);
       
$total = array_sum($typeTotals);
       
$tabSummary = [0 => $total] + $typeTotals;

       
$reactionFinder = $reactionRepo->findUserReactions($visitor)
            ->
limitByPage($page, $perPage, 1);

       
$reactionId = $this->filter('reaction_id', 'uint');

        if (
$reactionId)
        {
           
$reactionFinder->where('reaction_id', $reactionId);
        }
        else
        {
           
// showing all, but limit this to the keys that we got from the total to implicitly skip inactive entries
           
$reactionFinder->where('reaction_id', array_keys($typeTotals));
        }

       
/** @var ArrayCollection|\XF\Entity\ReactionContent[] $reactions */
       
$reactions = $reactionFinder->fetch();
       
$reactionRepo->addContentToReactions($reactions);
       
$reactions = $reactions->filter(function(\XF\Entity\ReactionContent $reaction)
        {
            return
$reaction->canView() && $reaction->isRenderable();
        });

       
$hasNext = count($reactions) > $perPage;
       
$reactions = $reactions->slice(0, $perPage);

       
$this->assertValidPage($page, $perPage, $total, 'account/reactions');

       
$viewParams = [
           
'tabSummary' => $tabSummary,

           
'activeReactionId' => $reactionId,
           
'reactions' => $reactions,
           
'hasNext' => $hasNext,
           
'page' => $page,

           
'listOnly' => $this->filter('list_only', 'bool')
        ];
       
$view = $this->view('XF:Account\Reactions', 'account_reactions', $viewParams);
        return
$this->addAccountWrapperParams($view, 'reactions');
    }

    public function
actionSecurity()
    {
       
$visitor = \XF::visitor();
       
$auth = $visitor->Auth->getAuthenticationHandler();
        if (!
$auth)
        {
            return
$this->noPermission();
        }

       
$redirect = $this->getDynamicRedirect($this->buildLink('account/security'), false);

        if (
$this->isPost())
        {
           
$passwordChange = $this->setupPasswordChange();
            if (!
$passwordChange->isValid($error))
            {
                return
$this->error($error);
            }

           
$passwordChange->setInvalidateRememberKeys(false); // about to handle this
           
$passwordChange->save();

           
$this->plugin('XF:Login')->handleVisitorPasswordChange();

            return
$this->redirect($redirect);
        }
        else
        {
           
/** @var \XF\Repository\Tfa $tfaRepo */
           
$tfaRepo = $this->repository('XF:Tfa');
           
$enabledProviders = [];
           
$userId = $visitor->user_id;

            foreach (
$tfaRepo->getValidProviderList($userId) AS $provider)
            {
                if (
$provider->isEnabled($userId))
                {
                   
$enabledProviders[] = $provider->getTitle();
                }
            }

           
$viewParams = [
               
'hasPassword' => $auth->hasPassword(),
               
'enabledTfaProviders' => $enabledProviders,
               
'isSecurityLocked' => boolval($visitor->security_lock),
               
'redirect' => $redirect
           
];

           
$view = $this->view('XF:Account\Security', 'account_security', $viewParams);
            return
$this->addAccountWrapperParams($view, 'security');
        }
    }

   
/**
     * @return \XF\Service\User\PasswordChange
     *
     * @throws \XF\Mvc\Reply\Exception
     */
   
protected function setupPasswordChange()
    {
       
$input = $this->filter([
           
'old_password' => 'str',
           
'password' => 'str',
           
'password_confirm' => 'str'
       
]);

       
$visitor = \XF::visitor();

        if (!
$visitor->authenticate($input['old_password']))
        {
            throw
$this->errorException(\XF::phrase('your_existing_password_is_not_correct'));
        }

        if (
$input['password'] !== $input['password_confirm'])
        {
            throw
$this->errorException(\XF::phrase('passwords_did_not_match'));
        }

        return
$this->service('XF:User\PasswordChange', $visitor, $input['password']);
    }

    public function
actionConnectedAccount()
    {
       
$visitor = \XF::visitor();
       
$auth = $visitor->Auth->getAuthenticationHandler();

       
$providers = $this->getConnectedAccountRepo()->getUsableProviders();

       
$viewParams = [
           
'providers' => $providers,
           
'hasPassword' => $auth->hasPassword()
        ];
       
$view = $this->view('XF:Account\Connected', 'account_connected', $viewParams);
        return
$this->addAccountWrapperParams($view, 'connected_account');
    }

    public function
actionConnectedAccountDisassociate(ParameterBag $params)
    {
       
$this->assertPostOnly();

       
$visitor = \XF::visitor();
       
$auth = $visitor->Auth->getAuthenticationHandler();
        if (!
$auth)
        {
            return
$this->noPermission();
        }

       
$connectedAccounts = $visitor->ConnectedAccounts;

       
$provider = $this->assertProviderExists($params->provider_id);
       
$handler = $provider->getHandler();

       
/** @var \XF\Entity\UserConnectedAccount $connectedAccount */
       
$connectedAccount = $connectedAccounts[$provider->provider_id] ?? null;
        if (
$this->filter('disassociate', 'bool') && $connectedAccount)
        {
           
$totalConnected = $connectedAccounts->count();

           
$connectedAccount->delete();

            if (!
$auth->hasPassword() && $totalConnected <= 1)
            {
               
$visitor->Auth->resetPassword();
               
$this->plugin('XF:Login')->handleVisitorPasswordChange();
               
$sendConfirmation = true;
            }
            else
            {
               
$sendConfirmation = false;
            }

           
$storageState = $handler->getStorageState($provider, $visitor);
           
$storageState->clearToken();

           
$profile = $visitor->getRelationOrDefault('Profile');
           
$profileConnectedAccounts = $profile->connected_accounts;
            unset(
$profileConnectedAccounts[$provider->provider_id]);
           
$profile->connected_accounts = $profileConnectedAccounts;

           
$visitor->save();

            if (
$sendConfirmation)
            {
               
/** @var \XF\Service\User\PasswordReset $passwordConfirmation */
               
$passwordConfirmation = $this->service('XF:User\PasswordReset', $visitor);
               
$passwordConfirmation->triggerConfirmation();
            }
        }
        return
$this->redirect($this->buildLink('account/connected-accounts'));
    }

    public function
actionRequestPassword()
    {
       
$visitor = \XF::visitor();
       
$auth = $visitor->Auth->getAuthenticationHandler();
        if (!
$auth)
        {
            return
$this->noPermission();
        }

        if (
$auth->hasPassword())
        {
            return
$this->error(\XF::phrase('your_account_already_has_password'));
        }

        if (
$this->isPost())
        {
           
$visitor->Auth->resetPassword();
           
$passwordConfirmation = $this->service('XF:User\PasswordReset', $visitor);
            if (!
$passwordConfirmation->canTriggerConfirmation($error))
            {
                return
$this->error($error);
            }

           
$passwordConfirmation->triggerConfirmation();
            return
$this->redirect($this->buildLink('account/security'));
        }
        else
        {
           
$view = $this->view('XF:Account\RequestPassword', 'account_request_password');
            return
$this->addAccountWrapperParams($view, 'security');
        }
    }

    public function
actionTwoStep()
    {
       
$this->assertTwoStepPasswordVerified();

       
/** @var \XF\Repository\Tfa $tfaRepo */
       
$tfaRepo = $this->repository('XF:Tfa');

       
/** @var \XF\ControllerPlugin\Login $loginPlugin */
       
$loginPlugin = $this->plugin('XF:Login');
       
$currentTrustKey = $loginPlugin->getCurrentTrustKey();

       
/** @var \XF\Repository\UserTfaTrusted $tfaTrustRepo */
       
$tfaTrustRepo = $this->repository('XF:UserTfaTrusted');

       
$visitor = \XF::visitor();
       
$userId = $visitor->user_id;

       
$viewParams = [
           
'providers' => $tfaRepo->getValidProviderList($userId),
           
'backupAdded' => $this->filter('backup', 'bool') && $visitor->Option->use_tfa,
           
'currentTrustRecord' => $tfaTrustRepo->getTfaTrustRecord($userId, $currentTrustKey),
           
'hasOtherTrusted' => $tfaTrustRepo->hasOtherTrustedDevices($userId, $currentTrustKey)
        ];
       
$view = $this->view('XF:Account\TwoStep', 'account_two_step', $viewParams);
        return
$this->addAccountWrapperParams($view, 'security');
    }

    public function
actionTwoStepBackupCodes()
    {
       
$this->assertTwoStepPasswordVerified();

       
/** @var \XF\Entity\TfaProvider $provider */
       
$provider = $this->em()->find('XF:TfaProvider', 'backup');
        if (!
$provider || !$provider->canManage())
        {
            return
$this->redirect($this->buildLink('account/two-step'));
        }

       
/** @var \XF\Tfa\Backup $handler */
       
$handler = $provider->handler;
       
$userConfig = $provider->getUserProviderConfig();
        if (!
$userConfig || empty($userConfig['codes']))
        {
            return
$this->redirect($this->buildLink('account/two-step'));
        }

       
$viewParams = [
           
'codes' => $handler->formatCodesForDisplay($userConfig['codes'])
        ];
       
$view = $this->view('XF:Account\TwoStepBackupCodes', 'account_two_step_backup_codes', $viewParams);
        return
$this->addAccountWrapperParams($view, 'security');
    }

    public function
actionTwoStepEnable(ParameterBag $params)
    {
       
$this->assertTwoStepPasswordVerified();
       
$this->assertPostOnly();

       
/** @var \XF\Entity\TfaProvider $provider */
       
$provider = $this->em()->find('XF:TfaProvider', $params->provider_id);
        if (!
$provider || !$provider->canEnable())
        {
            return
$this->redirect($this->buildLink('account/two-step'));
        }

       
$visitor = \XF::visitor();

       
/** @var \XF\Tfa\AbstractProvider $handler */
       
$handler = $provider->handler;
        if (!
$handler->meetsRequirements($visitor, $error))
        {
            return
$this->error($error);
        }

       
$sessionKey = 'tfaData_' . $provider->provider_id;
       
$session = $this->session();

       
$step = $this->filter('step', 'str');

        if (
$step == 'confirm')
        {
           
$providerData = $session->get($sessionKey);
            if (!
is_array($providerData))
            {
                return
$this->redirect($this->buildLink('account/two-step'));
            }

            if (!
$handler->verify('setup', $visitor, $providerData, $this->request))
            {
                return
$this->error(\XF::phrase('two_step_verification_value_could_not_be_confirmed'));
            }

           
/** @var \XF\Repository\Tfa $tfaRepo */
           
$tfaRepo = $this->repository('XF:Tfa');
           
$tfaRepo->enableUserTfaProvider($visitor, $provider, $providerData, $backupAdded);

           
/** @var \XF\Repository\IP $ipRepo */
           
$ipRepo = $this->repository('XF:Ip');
           
$ipRepo->logIp($visitor->user_id, $this->request->getIp(), 'user', $visitor->user_id, 'tfa_enable');

           
$session->remove($sessionKey);

            return
$this->redirect($this->buildLink('account/two-step', null, ['backup' => $backupAdded ? 1 : null]));
        }

       
$providerData = [];

        if (
$handler->requiresConfig())
        {
           
$result = $handler->handleConfig($this, $provider, \XF::visitor(), $providerData);
            if (
$result)
            {
                if (
$result instanceof \XF\Mvc\Reply\View)
                {
                   
$result = $this->addAccountWrapperParams($result, 'security');
                }
                return
$result;
            }
        }

       
$providerData = $handler->generateInitialData($visitor, $providerData);
       
$triggerData = $handler->trigger('setup', $visitor, $providerData, $this->request);

       
$session->set($sessionKey, $providerData);

       
$viewParams = [
           
'provider' => $provider,
           
'handler' => $handler,
           
'providerData' => $providerData,
           
'triggerData' => $triggerData
       
];
       
$view = $this->view('XF:Account\TwoStepEnable', 'account_two_step_enable', $viewParams);
        return
$this->addAccountWrapperParams($view, 'security');
    }

    public function
actionTwoStepDisable(ParameterBag $params)
    {
       
$this->assertTwoStepPasswordVerified();

        if (
$params->provider_id)
        {
           
/** @var \XF\Entity\TfaProvider $provider */
           
$provider = $this->em()->find('XF:TfaProvider', $params->provider_id);
            if (!
$provider || !$provider->canDisable())
            {
                return
$this->redirect($this->buildLink('account/two-step'));
            }
        }
        else
        {
           
$provider = null;
        }

        if (
$this->isPost())
        {
           
$visitor = \XF::visitor();

            if (
$provider)
            {
               
/** @var \XF\Entity\UserTfa|null $userTfa */
               
$userTfa = $provider->UserEntries[\XF::visitor()->user_id];
                if (
$userTfa)
                {
                   
$userTfa->delete();
                }
            }
            else
            {
               
/** @var \XF\Repository\Tfa $tfaRepo */
               
$tfaRepo = $this->repository('XF:Tfa');
               
$tfaRepo->disableTfaForUser(\XF::visitor());
            }

           
/** @var \XF\Repository\IP $ipRepo */
           
$ipRepo = $this->repository('XF:Ip');
           
$ipRepo->logIp($visitor->user_id, $this->request->getIp(), 'user', $visitor->user_id, 'tfa_disable');

            return
$this->redirect($this->buildLink('account/two-step'));
        }
        else
        {
           
$viewParams = [
               
'provider' => $provider
           
];
           
$view = $this->view('XF:Account\TwoStepDisable', 'account_two_step_disable', $viewParams);
            return
$this->addAccountWrapperParams($view, 'security');
        }
    }

    public function
actionTwoStepManage(ParameterBag $params)
    {
       
$this->assertTwoStepPasswordVerified();

       
/** @var \XF\Entity\TfaProvider $provider */
       
$provider = $this->em()->find('XF:TfaProvider', $params->provider_id);
        if (!
$provider || !$provider->canManage())
        {
            return
$this->redirect($this->buildLink('account/two-step'));
        }

       
/** @var \XF\Tfa\AbstractProvider $handler */
       
$handler = $provider->handler;
       
$result = $handler->handleManage($this, $provider, \XF::visitor(), $provider->getUserProviderConfig());
        if (!
$result)
        {
            return
$this->redirect($this->buildLink('account/two-step'));
        }

        if (
$result instanceof \XF\Mvc\Reply\View)
        {
           
$result = $this->addAccountWrapperParams($result, 'security');
        }
        return
$result;
    }

    public function
actionTwoStepTrustedDisable()
    {
       
$this->assertPostOnly();
       
$this->assertTwoStepPasswordVerified();

       
/** @var \XF\Repository\UserTfaTrusted $tfaTrustRepo */
       
$tfaTrustRepo = $this->repository('XF:UserTfaTrusted');

       
/** @var \XF\ControllerPlugin\Login $loginPlugin */
       
$loginPlugin = $this->plugin('XF:Login');
       
$currentTrustKey = $loginPlugin->getCurrentTrustKey();

       
$userId = \XF::visitor()->user_id;

        if (
$this->filter('others', 'bool'))
        {
           
$tfaTrustRepo->untrustOtherDevices($userId, $currentTrustKey);
        }
        else
        {
           
$tfaTrustRepo->untrustDevice($userId, $currentTrustKey);
        }

        return
$this->redirect($this->buildLink('account/two-step'));
    }

    protected function
assertTwoStepPasswordVerified()
    {
       
$this->assertPasswordVerified(3600, null, function($view)
        {
            return
$this->addAccountWrapperParams($view, 'security');
        });
    }

    public function
actionUpgrades()
    {
       
$purchasable = $this->em()->find('XF:Purchasable', 'user_upgrade', 'AddOn');
        if (!
$purchasable->isActive())
        {
            return
$this->message(\XF::phrase('no_account_upgrades_can_be_purchased_at_this_time'));
        }

       
$upgradeRepo = $this->repository('XF:UserUpgrade');
        list (
$available, $purchased) = $upgradeRepo->getFilteredUserUpgradesForList();

        if (!
$available && !$purchased)
        {
            return
$this->message(\XF::phrase('no_account_upgrades_can_be_purchased_at_this_time'));
        }

        if (\
XF::visitor()->user_state != 'valid')
        {
            return
$this->error(\XF::phrase('account_upgrades_cannot_be_purchased_account_unconfirmed'));
        }

       
/** @var \XF\Repository\Payment $paymentRepo */
       
$paymentRepo = $this->repository('XF:Payment');
       
$profiles = $paymentRepo->getPaymentProfileOptionsData();

       
$viewParams = [
           
'available' => $available,
           
'purchased' => $purchased,
           
'profiles' => $profiles
       
];
       
$view = $this->view('XF:Account\Upgrades', 'account_upgrades', $viewParams);
        return
$this->addAccountWrapperParams($view, 'upgrades');
    }

    public function
actionUpgradePurchase()
    {
       
$view = $this->view('XF:Account\UpgradePurchase', 'account_upgrade_purchase');
        return
$this->addAccountWrapperParams($view, 'upgrades');
    }

    public function
actionNewsFeed()
    {
        return
$this->redirectPermanently(
           
$this->buildLink('whats-new/news-feed')
        );
    }

    public function
actionAlerts()
    {
       
$this->assertNotEmbeddedImageRequest();

       
$visitor = \XF::visitor();

       
$page = $this->filterPage();
       
$perPage = $this->options()->alertsPerPage;

       
/** @var \XF\Repository\UserAlert $alertRepo */
       
$alertRepo = $this->repository('XF:UserAlert');

       
$alertsFinder = $alertRepo->findAlertsForUser($visitor->user_id);
       
$alerts = $alertsFinder->limitByPage($page, $perPage)->fetch();

       
$alertRepo->addContentToAlerts($alerts);
       
$alerts = $alerts->filterViewable();

       
$skipMarkRead = $this->filter('skip_mark_read', 'bool');
        if (
$page == 1 && $visitor->alerts_unviewed && !$skipMarkRead)
        {
           
$alertRepo->autoMarkUserAlertsRead($alerts, $visitor);
           
$alertRepo->markUserAlertsViewed($visitor);

           
$this->markInaccessibleAlertsReadIfNeeded($alerts);
        }

       
$viewParams = [
           
'alerts' => $alerts,

           
'page' => $page,
           
'perPage' => $perPage,
           
'totalAlerts' => $alertsFinder->total()
        ];
       
$view = $this->view('XF:Account\Alerts', 'account_alerts', $viewParams);
        return
$this->addAccountWrapperParams($view, 'alerts');
    }

    public function
actionAlertsPopup()
    {
       
$this->assertNotEmbeddedImageRequest();

       
$visitor = \XF::visitor();

       
/** @var \XF\Repository\UserAlert $alertRepo */
       
$alertRepo = $this->repository('XF:UserAlert');

       
$cutOff = \XF::$time - $this->options()->alertsPopupExpiryDays * 86400;
       
$alertsFinder = $alertRepo->findAlertsForUser($visitor->user_id, $cutOff);
       
$alerts = $alertsFinder->fetch(25);

       
$alertRepo->addContentToAlerts($alerts);

       
$this->markInaccessibleAlertsReadIfNeeded($alerts);

       
$alerts = $alerts->filterViewable();

       
$alertRepo->autoMarkUserAlertsRead($alerts, $visitor);

        if (
$visitor->alerts_unviewed)
        {
           
$alertRepo->markUserAlertsViewed($visitor);
        }

       
$viewParams = [
           
'alerts' => $alerts
       
];
        return
$this->view('XF:Account\AlertsPopup', 'account_alerts_popup', $viewParams);
    }

    protected function
markInaccessibleAlertsReadIfNeeded(\XF\Mvc\Entity\AbstractCollection $displayedAlerts = null)
    {
       
$visitor = \XF::visitor();

        if (!
$visitor->alerts_unread)
        {
            return;
        }

        if (
$displayedAlerts)
        {
           
$hasInaccessibleUnread = false;
           
$showingUnread = false;
            foreach (
$displayedAlerts AS $alert)
            {
               
/** @var \XF\Entity\UserAlert $alert */
               
if ($alert->isUnread())
                {
                   
$showingUnread = true;

                    if (!
$alert->canView())
                    {
                       
$hasInaccessibleUnread = true;
                    }
                }
            }

            if (
$showingUnread && !$hasInaccessibleUnread)
            {
               
// If we have unread on this page, we know we're still going to have some unread alerts left.
                // However, if we detect an inaccessible alert, let's do a check on the alerts to try to
                // sort out anything that might still be stuck.
               
return;
            }
        }

       
/** @var \XF\Repository\UserAlert $alertRepo */
       
$alertRepo = $this->repository('XF:UserAlert');

       
$alertRepo->markInaccessibleAlertsRead($visitor);
    }

    public function
actionAlertsMarkRead()
    {
       
$visitor = \XF::visitor();

       
/** @var \XF\Repository\UserAlert $alertRepo */
       
$alertRepo = $this->repository('XF:UserAlert');

       
$redirect = $this->getDynamicRedirect($this->buildLink('account/alerts'));

        if (
$this->isPost())
        {
           
$alertRepo->markUserAlertsRead($visitor);

            return
$this->redirect($redirect, \XF::phrase('all_alerts_marked_as_read'));
        }
        else
        {
           
$viewParams = [
               
'redirect' => $redirect
           
];
           
$view = $this->view('XF:Account\AlertsMarkRead', 'account_alerts_mark_read', $viewParams);
            return
$this->addAccountWrapperParams($view, 'alerts');
        }
    }

    public function
actionAlertToggle()
    {
       
$alertId = $this->filter('alert_id', 'uint');
       
$alert = $this->assertViewableAlert($alertId);

       
/** @var \XF\Repository\UserAlert $alertRepo */
       
$alertRepo = $this->repository('XF:UserAlert');

       
$newUnreadStatus = $this->filter('unread', '?bool');
        if (
$newUnreadStatus === null)
        {
           
$newUnreadStatus = $alert->isUnread() ? false : true;
        }

       
$redirect = $this->getDynamicRedirect($this->buildLink('account/alerts'));

        if (
$this->isPost())
        {
            if (
$newUnreadStatus)
            {
               
$alertRepo->markUserAlertUnread($alert);
               
$message = \XF::phrase('alert_marked_as_unread');
            }
            else
            {
               
$alertRepo->markUserAlertRead($alert);
               
$message = \XF::phrase('alert_marked_as_read');
            }

            return
$this->redirect($redirect, $message);
        }
        else
        {
           
$viewParams = [
               
'alert' => $alert,
               
'newUnreadStatus' => $newUnreadStatus,
               
'redirect' => $redirect
           
];
           
$view = $this->view('XF:Account\AlertToggle', 'account_alert_toggle', $viewParams);
            return
$this->addAccountWrapperParams($view, 'alerts');
        }
    }

    public function
actionBookmarks()
    {
       
$visitor = \XF::visitor();

        if (!
$visitor->canViewBookmarks())
        {
            return
$this->noPermission();
        }

        if (
$this->isPost())
        {
           
$label = $this->filter('label', 'str');
            return
$this->redirect($this->buildLink('account/bookmarks', null, ['label' => $label]));
        }

       
$page = $this->filterPage();
       
$perPage = 20;

       
/** @var \XF\Repository\Bookmark $bookmarkRepo */
       
$bookmarkRepo = $this->repository('XF:Bookmark');

       
$label = $this->filter('label', 'str');
        if (
$label)
        {
           
$bookmarksFinder = $bookmarkRepo->findBookmarksForUserByLabel($visitor->user_id, $label);
        }
        else
        {
           
$bookmarksFinder = $bookmarkRepo->findBookmarksForUser($visitor->user_id);
        }

       
// quite a big over-fetch but should mostly avoid pagination
        // issues resulting from invisible content
       
$bookmarks = $bookmarksFinder->limitByPage($page, $perPage, $perPage * 4)->fetch();

       
$bookmarkRepo->addContentToBookmarks($bookmarks);
       
$viewableBookmarks = $bookmarks->filterViewable();
       
$difference = min($bookmarks->count(), 20) - min($viewableBookmarks->count(), 20);
       
$bookmarks = $viewableBookmarks->slice($this->filter('difference', 'uint', 0), $perPage);

       
$labelFinder = $bookmarkRepo->findLabelsForUser($visitor->user_id);
       
$labels = $labelFinder->fetch()->pluckNamed('label', 'label');

       
$viewParams = [
           
'bookmarks' => $bookmarks,
           
'label' => $label,
           
'allLabels' => $labels,

           
'page' => $page,
           
'perPage' => $perPage,
           
'totalBookmarks' => $bookmarksFinder->total(),

           
'paginationDifference' => $difference
       
];
       
$view = $this->view('XF:Account\Bookmarks', 'account_bookmarks', $viewParams);
        return
$this->addAccountWrapperParams($view, 'bookmarks');
    }

    public function
actionBookmarksPopup()
    {
       
$visitor = \XF::visitor();

        if (!
$visitor->canViewBookmarks())
        {
            return
$this->noPermission();
        }

       
/** @var \XF\Repository\Bookmark $bookmarkRepo */
       
$bookmarkRepo = $this->repository('XF:Bookmark');

       
$label = $this->filter('label', 'str');
        if (
$label)
        {
           
$bookmarksFinder = $bookmarkRepo->findBookmarksForUserByLabel($visitor->user_id, $label);
        }
        else
        {
           
$bookmarksFinder = $bookmarkRepo->findBookmarksForUser($visitor->user_id);
        }

       
$bookmarks = $bookmarksFinder->fetch(25);

       
$bookmarkRepo->addContentToBookmarks($bookmarks);
       
$bookmarks = $bookmarks->filterViewable();

       
$labelFinder = $bookmarkRepo->findLabelsForUser($visitor->user_id);
       
$labels = $labelFinder->fetch()->pluckNamed('label', 'label');

       
$viewParams = [
           
'bookmarks' => $bookmarks,
           
'label' => $label,
           
'allLabels' => $labels
       
];
       
$view = $this->view('XF:Account\BookmarksPopup', 'account_bookmarks_popup', $viewParams);

       
$view->setJsonParam('showAllUrl', $this->buildLink('account/bookmarks', null, ['label' => $label]));

        return
$view;
    }

    public function
actionBookmarksAutoComplete()
    {
       
$visitor = \XF::visitor();

        if (!
$visitor->canViewBookmarks())
        {
            return
$this->noPermission();
        }

       
$q = $this->filter('q', 'str');

        if (
strlen($q) >= 2)
        {
           
$labels = $this->repository('XF:Bookmark')->getLabelAutoCompleteResults($q);

           
$results = [];
            foreach (
$labels AS $label)
            {
               
$results[] = [
                   
'id' => $label->label,
                   
'text' => $label->label,
                   
'q' => $q
               
];
            }
        }
        else
        {
           
$results = [];
        }
       
$view = $this->view();
       
$view->setJsonParam('results', $results);
        return
$view;
    }

    public function
actionVisitorMenu()
    {
       
$viewParams = [];
        return
$this->view('XF:Account\VisitorMenu', 'account_visitor_menu', $viewParams);
    }

    protected function
customFieldsSaveProcess(FormAction $form, $group, \XF\Entity\UserProfile $userProfile = null, $entitySave = false)
    {
        if (
$userProfile === null)
        {
           
$userProfile = \XF::visitor()->getRelationOrDefault('Profile');
        }

       
/** @var \XF\CustomField\Set $fieldSet */
       
$fieldSet = $userProfile->custom_fields;
       
$fieldDefinition = $fieldSet->getDefinitionSet()
            ->
filterGroup($group)
            ->
filterEditable($fieldSet, 'user');

       
$customFields = $this->filter('custom_fields', 'array');
       
$customFieldsShown = array_keys($fieldDefinition->getFieldDefinitions());

        if (
$customFieldsShown)
        {
           
$form->setup(function() use ($fieldSet, $customFields, $customFieldsShown)
            {
               
$fieldSet->bulkSet($customFields, $customFieldsShown);
            });
        }

        if (
$entitySave)
        {
           
$form->validateEntity($userProfile)->saveEntity($userProfile);
        }
    }

    public function
checkCsrfIfNeeded($action, ParameterBag $params)
    {
        if (
strtolower($action) == 'upgradepurchase')
        {
            return;
        }

       
parent::checkCsrfIfNeeded($action, $params);
    }

    public function
assertViewingPermissions($action) {}
    public function
assertTfaRequirement($action) {}

    public function
assertNotSecurityLocked($action)
    {
        switch (
strtolower($action))
        {
            case
'alertspopup':
            case
'dismissnotice':
            case
'requestpassword':
            case
'visitormenu':
                break;

            case
'security':
                if (\
XF::visitor()->security_lock === 'reset')
                {
                   
// don't allow direct password changes if a reset is pending
                   
parent::assertNotSecurityLocked($action);
                }
                break;

            default:
               
parent::assertNotSecurityLocked($action);
        }
    }

    public function
assertPolicyAcceptance($action)
    {
       
$action = strtolower($action);

        if (
strpos($action, 'twostep') === 0)
        {
            return;
        }

        switch (
$action)
        {
            case
'dismissnotice':
            case
'visitormenu':
                break;

            default:
               
parent::assertPolicyAcceptance($action);
        }
    }

    public function
assertBoardActive($action)
    {
        switch (
strtolower($action))
        {
            case
'dismissnotice':
            case
'visitormenu':
                break;

            default:
               
parent::assertBoardActive($action);
        }
    }

    public static function
getActivityDetails(array $activities)
    {
        return \
XF::phrase('managing_account_details');
    }

   
/**
     * @param string $id
     * @param array|string|null $with
     * @param null|string $phraseKey
     *
     * @return \XF\Mvc\Entity\Entity|\XF\Entity\ConnectedAccountProvider
     * @throws \XF\Mvc\Reply\Exception
     */
   
protected function assertProviderExists($id, $with = null, $phraseKey = null)
    {
        return
$this->assertRecordExists('XF:ConnectedAccountProvider', $id, $with, $phraseKey);
    }

   
/**
     * @param      $id
     * @param null $with
     * @param null $phraseKey
     *
     * @return \XF\Entity\UserAlert
     * @throws \XF\Mvc\Reply\Exception
     */
   
protected function assertViewableAlert($id, $with = null, $phraseKey = null)
    {
       
$alert = $this->assertRecordExists('XF:UserAlert', $id, $with, $phraseKey);

        if (
$alert->alerted_user_id != \XF::visitor()->user_id)
        {
            throw
$this->exception($this->notFound());
        }

        if (!
$alert->canView())
        {
            throw
$this->exception($this->noPermission());
        }

        return
$alert;
    }

   
/**
     * @return \XF\Repository\ConnectedAccount
     */
   
protected function getConnectedAccountRepo()
    {
        return
$this->repository('XF:ConnectedAccount');
    }
}