/home/ivoiecob/email.hirewise-va.com/modules/CpanelIntegrator/Module.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\Modules\CpanelIntegrator;

use Aurora\Modules\Core\Classes\Tenant;
use Aurora\Modules\Core\Module as CoreModule;
use Aurora\Modules\Mail\Module as MailModule;
use Aurora\Modules\MailDomains\Classes\Domain;
use Aurora\System\Exceptions\ApiException;
use Aurora\System\Notifications;

/**
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2023, Afterlogic Corp.
 *
 * @property Settings $oModuleSettings
 *
 * @package Modules
 */
class Module extends \Aurora\System\Module\AbstractModule
{
    public const UAPI = '3';
    private $oCpanel = [];

    protected $aManagers = [
        'Aliases' => null
    ];

    public $oMailModule = null;

    /**
     * @return Module
     */
    public static function getInstance()
    {
        return parent::getInstance();
    }

    /**
     * @return Module
     */
    public static function Decorator()
    {
        return parent::Decorator();
    }

    /**
     * @return Settings
     */
    public function getModuleSettings()
    {
        return $this->oModuleSettings;
    }

    public function init()
    {
        $this->aErrors = [
            Enums\ErrorCodes::DomainOutsideTenant		=> $this->i18N('ERROR_DOMAIN_OUTSIDE_TENANT'),
            Enums\ErrorCodes::AliaMatchesExistingEmail	=> $this->i18N('ERROR_CREATE_ALIAS'),
            Enums\ErrorCodes::AliasAlreadyExists		=> $this->i18N('ERROR_ALIAS_ALREADY_EXISTS'),
        ];
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($this->oModuleSettings->AllowCreateDeleteAccountOnCpanel && $oAuthenticatedUser &&
                ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
            // Subscription shouldn't work for Anonymous because Signup subscription will work
            // Subscription shouldn't work for Normal user because CPanel account should be created only for first user account
            $this->subscribeEvent('Mail::CreateAccount::before', array($this, 'onBeforeCreateAccount'));
            $this->subscribeEvent('Mail::BeforeDeleteAccount', array($this, 'onBeforeDeleteAccount'));
        }
        $this->subscribeEvent('MailSignup::Signup::before', [$this, 'onAfterSignup']);
        $this->subscribeEvent('Mail::Account::ToResponseArray', array($this, 'onMailAccountToResponseArray'));
        $this->subscribeEvent('ChangeAccountPassword', array($this, 'onChangeAccountPassword'));
        $this->subscribeEvent('Mail::UpdateForward::before', array($this, 'onBeforeUpdateForward'));
        $this->subscribeEvent('Mail::GetForward::before', array($this, 'onBeforeGetForward'));
        $this->subscribeEvent('Mail::GetAutoresponder::before', array($this, 'onBeforeGetAutoresponder'));
        $this->subscribeEvent('Mail::UpdateAutoresponder::before', array($this, 'onBeforeUpdateAutoresponder'));
        $this->subscribeEvent('Mail::GetFilters::before', array($this, 'onBeforeGetFilters'));
        $this->subscribeEvent('Mail::UpdateFilters::before', array($this, 'onBeforeUpdateFilters'));
        $this->subscribeEvent('Mail::UpdateQuota', array($this, 'onUpdateQuota'));
        $this->subscribeEvent('Mail::SaveMessage::before', array($this, 'onBeforeSendOrSaveMessage'));
        $this->subscribeEvent('Mail::SendMessage::before', array($this, 'onBeforeSendOrSaveMessage'));

        $this->subscribeEvent('Mail::IsEmailAllowedForCreation::after', array($this, 'onAfterIsEmailAllowedForCreation'));
    }

    /**
     * @param string $sManager
     * @return object
     */
    public function getManager($sManager)
    {
        if ($this->aManagers[$sManager] === null) {
            $sManagerClass = Module::getNamespace() . "\\Managers\\" . $sManager;
            $this->aManagers[$sManager] = new $sManagerClass($this);
        }

        return $this->aManagers[$sManager];
    }

    /**
     * Connects to cPanel. Uses main credentials if $iTenantId has not been passed, otherwise - tenant credentials to cPanel.
     * @param int $iTenantId
     * @return object
     */
    protected function getCpanel($iTenantId = 0)
    {
        if (!isset($this->oCpanel[$iTenantId])) {
            $sHost = $this->oModuleSettings->CpanelHost;
            $sPort = $this->oModuleSettings->CpanelPort;
            $sUser = $this->oModuleSettings->CpanelUser;
            $sPassword = $this->oModuleSettings->CpanelPassword;
            if ($sPassword && !\Aurora\System\Utils::IsEncryptedValue($sPassword)) {
                $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
                $this->Decorator()->UpdateSettings($sHost, $sPort, $sUser, \Aurora\System\Utils::EncryptValue($sPassword), null);
                $bPrevState = \Aurora\System\Api::skipCheckUserRole($bPrevState);
            } else {
                $sPassword = \Aurora\System\Utils::DecryptValue($sPassword);
            }

            $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
            if (\Aurora\System\Api::GetSettings()->GetValue('EnableMultiTenant') && $iTenantId !== 0 && $oAuthenticatedUser && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
                $oSettings = $this->oModuleSettings;
                $oTenant = \Aurora\System\Api::getTenantById($iTenantId);
                $sHost = $oSettings->GetTenantValue($oTenant->Name, 'CpanelHost', '');
                $sPort = $oSettings->GetTenantValue($oTenant->Name, 'CpanelPort', '');
                $sUser = $oSettings->GetTenantValue($oTenant->Name, 'CpanelUser', '');
                $sPassword = $oSettings->GetTenantValue($oTenant->Name, 'CpanelPassword', '');
                if ($sPassword && !\Aurora\System\Utils::IsEncryptedValue($sPassword)) {
                    if ($oSettings->IsTenantSettingsExists($oTenant->Name)) {
                        $this->Decorator()->UpdateSettings($sHost, $sPort, $sUser, \Aurora\System\Utils::EncryptValue($sPassword), $iTenantId);
                    }
                } else {
                    $sPassword = \Aurora\System\Utils::DecryptValue($sPassword);
                }
            }

            $this->oCpanel[$iTenantId] = new \Gufy\CpanelPhp\Cpanel([
                'host' => "https://" . $sHost . ":" . $sPort,
                'username' => $sUser,
                'auth_type' => 'password',
                'password' => $sPassword,
            ]);

            $this->oCpanel[$iTenantId]->setTimeout(30);
        }

        return $this->oCpanel[$iTenantId];
    }

    /**
     * Executes cPanel command, logs parameters and the result.
     * @param object $oCpanel
     * @param string $sModule
     * @param string $sFunction
     * @param array $aParams
     * @return string
     */
    protected function executeCpanelAction($oCpanel, $sModule, $sFunction, $aParams)
    {
        // There are no params in log because they can contain private data (password for example)
        \Aurora\System\Api::Log('cPanel execute action. Module ' . $sModule . '. Function ' . $sFunction);
        $sResult = $oCpanel->execute_action(self::UAPI, $sModule, $sFunction, $oCpanel->getUsername(), $aParams);
        \Aurora\System\Api::Log($sResult);
        return $sResult;
    }

    /**
     * Creates account with credentials specified in registration form
     *
     * @param array $aArgs New account credentials.
     * @param mixed $mResult Is passed by reference.
     */
    public function onAfterSignup($aArgs, &$mResult)
    {
        if (isset($aArgs['Login']) && isset($aArgs['Password']) && !empty(trim($aArgs['Password'])) && !empty(trim($aArgs['Login']))) {
            $sLogin = trim($aArgs['Login']);
            $sPassword = trim($aArgs['Password']);
            $sFriendlyName = isset($aArgs['Name']) ? trim($aArgs['Name']) : '';
            $bSignMe = isset($aArgs['SignMe']) ? (bool) $aArgs['SignMe'] : false;
            $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
            $iUserId = \Aurora\Modules\Core\Module::Decorator()->CreateUser(0, $sLogin);
            $oUser = \Aurora\System\Api::getUserById((int) $iUserId);
            $oCpanel = null;
            if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                try {
                    $oCpanel = $this->getCpanel($oUser->IdTenant);
                } catch(\Exception $oException) {
                }

                if ($oCpanel) {
                    $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oUser->PublicId);
                    if (!empty($sDomain) && $this->isDomainSupported($sDomain)) {
                        $iQuota = (int) $this->oModuleSettings->UserDefaultQuotaMB;
                        try {
                            $sCpanelResponse = $this->executeCpanelAction(
                                $oCpanel,
                                'Email',
                                'add_pop',
                                [
                                    'email' => $sLogin,
                                    'password' => $sPassword,
                                    'quota'	=> $iQuota,
                                    'domain' => $sDomain
                                ]
                            );
                            $aParseResult = self::parseResponse($sCpanelResponse, false);
                        } catch(\Exception $oException) {
                            throw new ApiException(0, $oException, $oException->getMessage());
                        }
                        if (is_array($aParseResult) && isset($aParseResult['Data']) && !empty($aParseResult['Data'])) {
                            try {
                                $oAccount = \Aurora\Modules\Mail\Module::Decorator()->CreateAccount($oUser->Id, $sFriendlyName, $sLogin, $sLogin, $sPassword);
                                if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount) {
                                    $iTime = $bSignMe ? 0 : time();
                                    $sAuthToken = \Aurora\System\Api::UserSession()->Set(
                                        [
                                            'token'		=> 'auth',
                                            'sign-me'		=> $bSignMe,
                                            'id'			=> $oAccount->IdUser,
                                            'account'		=> $oAccount->Id,
                                            'account_type'	=> $oAccount->getName()
                                        ],
                                        $iTime
                                    );
                                    $mResult = [\Aurora\System\Application::AUTH_TOKEN_KEY => $sAuthToken];
                                }
                            } catch (\Exception $oException) {
                                if ($oException instanceof \Aurora\Modules\Mail\Exceptions\Exception &&
                                    $oException->getCode() === \Aurora\Modules\Mail\Enums\ErrorCodes::CannotLoginCredentialsIncorrect) {
                                    \Aurora\Modules\Core\Module::Decorator()->DeleteUser($oUser->Id);
                                }
                                throw $oException;
                            }
                        } elseif (is_array($aParseResult) && isset($aParseResult['Error'])) {
                            //If Account wasn't created - delete user
                            $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
                            \Aurora\Modules\Core\Module::Decorator()->DeleteUser($oUser->Id);
                            \Aurora\System\Api::skipCheckUserRole($bPrevState);
                            throw new \Exception($aParseResult['Error']);
                        }
                    }
                }
            }
            \Aurora\System\Api::skipCheckUserRole($bPrevState);
        }

        return true; // break subscriptions to prevent account creation in other modules
    }

    /**
     * Creates cPanel account before mail account will be created in the system.
     * @param array $aArgs
     * @param mixed $mResult
     * @throws \Exception
     */
    public function onBeforeCreateAccount($aArgs, &$mResult)
    {
        $iUserId = $aArgs['UserId'];
        $oUser = \Aurora\System\Api::getUserById($iUserId);
        $sAccountEmail = $aArgs['Email'];
        if ($this->isAliasExists($sAccountEmail, $oUser->IdTenant)) {
            throw new \Exception($this->i18N('ERROR_EMAIL_MATCHES_ALIAS'));
        }
        if ($oUser instanceof \Aurora\Modules\Core\Models\User && $sAccountEmail === $oUser->PublicId) {
            $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sAccountEmail);
            if (!empty($sDomain) && $this->isDomainSupported($sDomain)) {
                $aTenantQuota = \Aurora\Modules\Mail\Module::Decorator()->GetEntitySpaceLimits('Tenant', $oUser->Id, $oUser->IdTenant);
                $iQuota = $aTenantQuota ? $aTenantQuota['UserSpaceLimitMb'] : (int) $this->oModuleSettings->UserDefaultQuotaMB;
                $oCpanel = $this->getCpanel($oUser->IdTenant);
                $sCpanelResponse = $this->executeCpanelAction(
                    $oCpanel,
                    'Email',
                    'add_pop',
                    [
                        'email' => $aArgs['IncomingLogin'],
                        'password' => $aArgs['IncomingPassword'],
                        'quota' => $iQuota,
                        'domain' => $sDomain
                    ]
                );
                $aResult = self::parseResponse($sCpanelResponse, false);
                if ($aResult['Status'] === false && isset($aResult['Error']) && !empty($aResult['Error']) && strrpos(strtolower($aResult['Error']), 'exists') === false) {
                    throw new \Exception($aResult['Error']);
                }
            }
        }
    }

    /**
     * Deletes cPanel account, its aliases, forward, autoresponder and filters.
     * @param array $aArgs
     * @param mixed $mResult
     */
    public function onBeforeDeleteAccount($aArgs, &$mResult)
    {
        $oAccount = $aArgs['Account'];
        $oUser = $aArgs['User'];
        if ($oUser instanceof \Aurora\Modules\Core\Models\User
                && $oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)
                && $oAccount->Email === $oUser->PublicId
        ) {
            $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
            if ($sDomain !== '') {
                $aAliases = self::Decorator()->GetAliases($oUser->Id);
                if (isset($aAliases['Aliases']) && is_array($aAliases['Aliases']) && count($aAliases['Aliases']) > 0) {
                    self::Decorator()->DeleteAliases($oUser->Id, $aAliases['Aliases']);
                }

                $aForwardArgs = [
                    'AccountID' => $oAccount->Id,
                    'Enable' => false,
                    'Email' => '',
                ];
                $mForwardResult = false;
                $this->onBeforeUpdateForward($aForwardArgs, $mForwardResult);

                $this->deleteAutoresponder($oAccount->Email, $oUser->IdTenant);

                //Checking if an account exists on CPanel
                $oCpanel = $this->getCpanel($oUser->IdTenant);
                if ($oCpanel) {
                    $sCpanelResponse = $this->executeCpanelAction(
                        $oCpanel,
                        'Email',
                        'list_pops',
                        ['regex' => $oAccount->Email]
                    );
                    $aParseResult = self::parseResponse($sCpanelResponse);
                    //Trying to delete an Account only if it exists on CPanel
                    if (
                        $aParseResult
                        && isset($aParseResult['Data'])
                        && is_array($aParseResult['Data'])
                        && count($aParseResult['Data']) > 0
                        && $this->oModuleSettings->AllowCreateDeleteAccountOnCpanel
                    ) {
                        $sCpanelResponse = $this->executeCpanelAction(
                            $oCpanel,
                            'Email',
                            'delete_pop',
                            [
                                'email' => $oAccount->Email,
                                'domain' => $sDomain,
                            ]
                        );
                        self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
                    }
                }
            }
        }
    }

    /**
     * Updates mail quota on cPanel.
     * @param array $aArgs
     * @param mixed $mResult
     */
    public function onUpdateQuota($aArgs, &$mResult)
    {
        $iTenantId = $aArgs['TenantId'];
        $sEmail = $aArgs['Email'];
        $iQuota = $aArgs['QuotaMb'];
        $oCpanel = $this->getCpanel($iTenantId);
        $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail);
        $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
        if ($this->isDomainSupported($sDomain)) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'edit_pop_quota',
                [
                    'email' => $sLogin,
                    'domain' => $sDomain,
                    'quota' => $iQuota
                ]
            );
            $mResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
        }
    }

    public function onBeforeSendOrSaveMessage(&$aArgs, &$mResult)
    {
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAlias = $this->getManager('Aliases')->getAlias((int) $aArgs['AliasID']);
        if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias
            && $oUser instanceof \Aurora\Modules\Core\Models\User
            && $oAlias->IdUser === $oUser->Id
        ) {
            $aArgs['Alias'] = $oAlias;
        }
    }

    /**
     * Adds to account response array information about if it is allowed to change the password for this account.
     * @param array $aArguments
     * @param mixed $mResult
     */
    public function onMailAccountToResponseArray($aArguments, &$mResult)
    {
        $oAccount = $aArguments['Account'];

        if ($oAccount && $this->isAccountServerSupported($oAccount)) {
            if (!isset($mResult['Extend']) || !is_array($mResult['Extend'])) {
                $mResult['Extend'] = [];
            }
            $mResult['Extend']['AllowChangePasswordOnMailServer'] = true;

            if (class_exists('\Aurora\Modules\Mail\Module')) {
                $oMailModule = \Aurora\Modules\Mail\Module::getInstance();

                $mResult['AllowFilters'] = $oMailModule->oModuleSettings->AllowFilters;
                $mResult['AllowForward'] = $oMailModule->oModuleSettings->AllowForward;
                $mResult['AllowAutoresponder'] = $oMailModule->oModuleSettings->AllowAutoresponder;
            }

            $oUser = \Aurora\Api::getUserById($oAccount->IdUser);
            if ($oAccount->Email === $oUser->PublicId) {
                try {
                    $aAliases = self::Decorator()->GetAliases($oAccount->IdUser);
                    $mResult['Extend']['Aliases'] = is_array($aAliases) && isset($aAliases['Aliases']) ? $aAliases['Aliases'] : [];
                } catch (\Exception $oException) {
                }
            }
        }
    }

    /**
     * Tries to change password for account if it is allowed.
     * @param array $aArguments
     * @param mixed $mResult
     */
    public function onChangeAccountPassword($aArguments, &$mResult)
    {
        $bPasswordChanged = false;
        $bBreakSubscriptions = false;

        $oAccount = $aArguments['Account'];
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)
                && ($oAccount->getPassword() === $aArguments['CurrentPassword'])) {
            $bPasswordChanged = $this->changePassword($oAccount, $aArguments['NewPassword']);
            $bBreakSubscriptions = true; // break if Cpanel plugin tries to change password in this account.
        }

        if (is_array($mResult)) {
            $mResult['AccountPasswordChanged'] = $mResult['AccountPasswordChanged'] || $bPasswordChanged;
        }

        return $bBreakSubscriptions;
    }

    /**
     * Updates forward for specified account.
     * @param array $aArgs
     * @param mixed $mResult
     * @return boolean
     * @throws \Aurora\System\Exceptions\ApiException
     */
    public function onBeforeUpdateForward($aArgs, &$mResult)
    {
        if (!isset($aArgs['AccountID']) || !isset($aArgs['Enable']) || !isset($aArgs['Email'])
                || $aArgs['Enable'] && (empty(trim($aArgs['Email'])) || !filter_var(trim($aArgs['Email']), FILTER_VALIDATE_EMAIL))) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        $sToEmail = trim($aArgs['Email']);

        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAccount = MailModule::getInstance()->GetAccount($aArgs['AccountID']);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
            // check if account belongs to authenticated user
            && ($oAccount->IdUser === $oAuthenticatedUser->Id
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
                $oCpanel = null;
                $oUser = \Aurora\Api::getUserById($oAccount->IdUser);
                if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                    $oCpanel = $this->getCpanel($oUser->IdTenant);
                }
                if ($oCpanel) {
                    $sFromEmail = $oAccount->Email;
                    $sFromDomain = \MailSo\Base\Utils::GetDomainFromEmail($sFromEmail);

                    // check if there is forwarder on cPanel
                    $aResult = $this->getForwarder($sFromDomain, $sFromEmail, $oUser->IdTenant);
                    $bForwarderExists = is_array($aResult) && isset($aResult['Email']) && !empty($aResult['Email']);
                    $sOldToEmail = $bForwarderExists ? $aResult['Email'] : '';
                    // "Enable" indicates if forwarder should exist on cPanel
                    if ($aArgs['Enable']) {
                        if ($bForwarderExists) {
                            $bSameForwarderExists = $bForwarderExists && $sOldToEmail === $sToEmail;
                            if ($bSameForwarderExists) {
                                $mResult = true;
                            } elseif ($this->deleteForwarder($sFromEmail, $sOldToEmail, $oUser->IdTenant)) {
                                $mResult = $this->createForwarder($sFromDomain, $sFromEmail, $sToEmail, $oUser->IdTenant);
                            }
                        } else {
                            $mResult = $this->createForwarder($sFromDomain, $sFromEmail, $sToEmail, $oUser->IdTenant);
                        }
                    } else {
                        if ($bForwarderExists) {
                            $mResult = $this->deleteForwarder($sFromEmail, $sOldToEmail, $oUser->IdTenant);
                        } else {
                            $mResult = true;
                        }
                    }
                }
            }

            return true; // breaking subscriptions to prevent update in parent module
        }

        return false;
    }

    /**
     * Obtains forward data for specified account.
     * @param array $aArgs
     * @param mixed $mResult
     * @return boolean
     */
    public function onBeforeGetForward($aArgs, &$mResult)
    {
        $mResult = false;

        if (isset($aArgs['AccountID'])) {
            $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
            $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($aArgs['AccountID']);
            if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                    && $this->isAccountServerSupported($oAccount)) {
                if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
                    // check if account belongs to authenticated user
                    && $oAccount->IdUser === $oAuthenticatedUser->Id) {
                    $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
                    $aResult = $this->getForwarder($sDomain, $oAccount->Email, $oAuthenticatedUser->IdTenant);

                    if (is_array($aResult)) {
                        if (isset($aResult['Email'])) {
                            $mResult = [
                                'Enable' => true,
                                'Email' => $aResult['Email']
                            ];
                        } else {
                            $mResult = [
                                'Enable' => false,
                                'Email' => ''
                            ];
                        }
                    }
                }
                return true; // breaking subscriptions to prevent update in parent module
            }
        }

        return false;
    }

    /**
     * Obtains autoresponder for specified account.
     * @param array $aArgs
     * @param mixed $mResult
     * @return boolean
     */
    public function onBeforeGetAutoresponder($aArgs, &$mResult)
    {
        $mResult = false;

        if (isset($aArgs['AccountID'])) {
            $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
            $oAccount = MailModule::getInstance()->GetAccount($aArgs['AccountID']);
            if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                    && $this->isAccountServerSupported($oAccount)) {
                if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
                    // check if account belongs to authenticated user
                    && $oAccount->IdUser === $oAuthenticatedUser->Id) {
                    $mAutoresponder = $this->getAutoresponder($oAccount->Email, $oAuthenticatedUser->IdTenant);

                    if (is_array($mAutoresponder) && isset($mAutoresponder['Enable'])) {
                        $mResult = [
                            'Enable' => $mAutoresponder['Enable'],
                            'Subject' => $mAutoresponder['Subject'],
                            'Message' => $mAutoresponder['Message']
                        ];
                    }
                }
                return true; // breaking subscriptions to prevent update in parent module
            }
        }

        return false;
    }

    /**
     * Updates autoresponder data.
     * @param array $aArgs
     * @param mixed $mResult
     * @return boolean
     * @throws \Aurora\System\Exceptions\ApiException
     */
    public function onBeforeUpdateAutoresponder($aArgs, &$mResult)
    {
        if (!isset($aArgs['AccountID']) || !isset($aArgs['Enable']) || !isset($aArgs['Subject']) || !isset($aArgs['Message'])
                || $aArgs['Enable'] && (empty(trim($aArgs['Subject'])) || empty(trim($aArgs['Message'])))) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        $mResult = false;

        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAccount = MailModule::getInstance()->GetAccount($aArgs['AccountID']);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)) {
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
                // check if account belongs to authenticated user
                && $oAccount->IdUser === $oAuthenticatedUser->Id) {
                $sSubject = trim($aArgs['Subject']);
                $sMessage = trim($aArgs['Message']);
                $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
                $mResult = $this->updateAutoresponder($sDomain, $oAccount->Email, $sSubject, $sMessage, $aArgs['Enable'], $oAuthenticatedUser->IdTenant);
            }

            return true; // breaking subscriptions to prevent update in parent module
        }

        return false;
    }

    /**
     * Obtains filters for specified account.
     * @param array $aArgs
     * @param mixed $mResult
     * @return boolean
     */
    public function onBeforeGetFilters($aArgs, &$mResult)
    {
        if (!isset($aArgs['AccountID'])) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($aArgs['AccountID']);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)) {
            $mResult = [];

            $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
            // check if account belongs to authenticated user
            && $oAccount->IdUser === $oAuthenticatedUser->Id) {
                $oCpanel = $this->getCpanel($oAuthenticatedUser->IdTenant);
                if ($oCpanel) {
                    $mResult = $this->getFilters($oAccount, $oAuthenticatedUser->IdTenant);
                }
            }

            return true; // breaking subscriptions to prevent update in parent module
        }

        return false;
    }

    /**
     * Updates filters.
     * @param array $aArgs
     * @param mixed $mResult
     * @return boolean
     * @throws \Aurora\System\Exceptions\ApiException
     */
    public function onBeforeUpdateFilters($aArgs, &$mResult)
    {
        if (!isset($aArgs['AccountID']) || !isset($aArgs['Filters']) || !is_array($aArgs['Filters'])) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }

        $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($aArgs['AccountID']);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)) {
            $mResult = false;

            $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
                // check if account belongs to authenticated user
                && $oAccount->IdUser === $oAuthenticatedUser->Id) {
                $oCpanel = $this->getCpanel($oAuthenticatedUser->IdTenant);
                if ($oCpanel && $this->removeSupportedFilters($oAccount, $oAuthenticatedUser->IdTenant)) {
                    if (count($aArgs['Filters']) === 0) {
                        $mResult = true;
                    } else {
                        foreach ($aArgs['Filters'] as $aWebmailFilter) {
                            $aFilterProperty = self::convertWebmailFIlterToCPanelFIlter($aWebmailFilter, $oAccount);
                            //create filter
                            $sCpanelResponse = $this->executeCpanelAction(
                                $oCpanel,
                                'Email',
                                'store_filter',
                                $aFilterProperty
                            );
                            $aCreationResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
                            //disable filter if needed
                            if (!$aCreationResult['Status']) {
                                $mResult = false;
                                break;
                            }
                            if (!$aWebmailFilter['Enable']) {
                                $sCpanelResponse = $this->executeCpanelAction(
                                    $oCpanel,
                                    'Email',
                                    'disable_filter',
                                    [
                                        'account' => $oAccount->Email,
                                        'filtername' => $aFilterProperty['filtername']
                                    ]
                                );
                                self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
                            }
                            $mResult = true;
                        }
                    }
                }
            }

            return true; // breaking subscriptions to prevent update in parent module
        }

        return false;
    }

    public function onAfterIsEmailAllowedForCreation($aArgs, &$mResult)
    {
        if ($mResult && isset($aArgs['Email'])) {
            $iTenantId = 0;
            $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserByPublicId($aArgs['Email']);
            if ($oUser) {
                $iTenantId = $oUser->IdTenant;
            }
            $mResult = !$this->isAliasExists($aArgs['Email'], $iTenantId);
        }
    }

    /**
     * Checks if the CpanelIntegrator module can work with the specified domain.
     * @param string $sDomain
     * @return bool
     */
    protected function isDomainSupported($sDomain)
    {
        $bSupported = in_array('*', $this->oModuleSettings->SupportedServers);

        if (!$bSupported) {
            $aGetMailServerResult = MailModule::Decorator()->GetMailServerByDomain($sDomain, /*AllowWildcardDomain*/true);
            if (!empty($aGetMailServerResult) && isset($aGetMailServerResult['Server']) && $aGetMailServerResult['Server'] instanceof \Aurora\Modules\Mail\Models\Server) {
                $bSupported = in_array($aGetMailServerResult['Server']->IncomingServer, $this->oModuleSettings->SupportedServers);
            }
        }

        return $bSupported;
    }

    /**
     * Checks if the CpanelIntegrator module can work with the server of the specified account.
     * @param \Aurora\Modules\Mail\Models\MailAccount $oAccount
     * @return bool
     */
    protected function isAccountServerSupported($oAccount)
    {
        $bSupported = in_array('*', $this->oModuleSettings->SupportedServers);

        if (!$bSupported) {
            $oServer = $oAccount->getServer();
            if ($oServer instanceof \Aurora\Modules\Mail\Models\Server) {
                $bSupported = in_array($oServer->IncomingServer, $this->oModuleSettings->SupportedServers);
            }
        }

        return $bSupported;
    }

    /**
     * Tries to change password for account.
     * @param \Aurora\Modules\Mail\Models\MailAccount $oAccount
     * @param string $sPassword
     * @return boolean
     * @throws \Aurora\System\Exceptions\ApiException
     */
    protected function changePassword($oAccount, $sPassword)
    {
        $bResult = false;

        if (0 < strlen($oAccount->IncomingPassword) && $oAccount->IncomingPassword !== $sPassword) {
            $cpanel_host = $this->oModuleSettings->CpanelHost;
            $cpanel_user = $this->oModuleSettings->CpanelUser;
            $cpanel_pass = $this->oModuleSettings->CpanelPassword;
            $cpanel_user0 = null;
            if ($cpanel_pass && !\Aurora\System\Utils::IsEncryptedValue($cpanel_pass)) {
                $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
                $this->Decorator()->UpdateSettings($cpanel_host, $this->oModuleSettings->CpanelPort, $cpanel_user, \Aurora\System\Utils::EncryptValue($cpanel_pass), null);
                $bPrevState = \Aurora\System\Api::skipCheckUserRole($bPrevState);
            } else {
                $cpanel_pass = \Aurora\System\Utils::DecryptValue($cpanel_pass);
            }

            $oSettings = &\Aurora\System\Api::GetSettings();
            $oUser = \Aurora\System\Api::getUserById($oAccount->IdUser);
            $oTenant = $oUser instanceof \Aurora\Modules\Core\Models\User ? \Aurora\System\Api::getTenantById($oUser->IdTenant) : null;
            if ($oSettings->GetValue('EnableMultiTenant') && $oTenant instanceof \Aurora\Modules\Core\Models\Tenant) {
                $cpanel_host = $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelHost', '');
                $cpanel_user = $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelUser', '');
                $cpanel_pass = $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelPassword', '');
                if ($cpanel_pass && !\Aurora\System\Utils::IsEncryptedValue($cpanel_pass)) {
                    if ($this->oModuleSettings->IsTenantSettingsExists($oTenant->Name)) {
                        $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
                        $this->Decorator()->UpdateSettings($cpanel_host, $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelPort'), $cpanel_user, \Aurora\System\Utils::EncryptValue($cpanel_pass), $oTenant->Id);
                        $bPrevState = \Aurora\System\Api::skipCheckUserRole($bPrevState);
                    }
                } else {
                    $cpanel_pass = \Aurora\System\Utils::DecryptValue($cpanel_pass);
                }
            }

            $email_user = urlencode($oAccount->Email);
            $email_pass = urlencode($sPassword);
            $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);

            if ($cpanel_user == 'root') {
                $query = 'https://' . $cpanel_host . ':2087/json-api/listaccts?api.version=1&searchtype=domain&search=' . $sDomain;

                $curl = curl_init();
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
                curl_setopt($curl, CURLOPT_HEADER, 0);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
                $header[0] = 'Authorization: Basic ' . base64_encode($cpanel_user . ':' . $cpanel_pass) . "\n\r";
                curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
                curl_setopt($curl, CURLOPT_URL, $query);
                $result = curl_exec($curl);
                if ($result == false) {
                    \Aurora\System\Api::Log('curl_exec threw error "' . curl_error($curl) . '" for ' . $query);
                    curl_close($curl);
                    throw new ApiException(Notifications::CanNotChangePassword);
                } else {
                    curl_close($curl);
                    \Aurora\System\Api::Log('..:: QUERY0 ::.. ' . $query);
                    $json_res = json_decode($result, true);
                    \Aurora\System\Api::Log('..:: RESULT0 ::.. ' . $result);
                    if (isset($json_res['data']['acct'][0]['user'])) {
                        $cpanel_user0 = $json_res['data']['acct'][0]['user'];
                        \Aurora\System\Api::Log('..:: USER ::.. ' . $cpanel_user0);
                    }
                }
                $query = 'https://' . $cpanel_host . ':2087/json-api/cpanel?cpanel_jsonapi_user=' . $cpanel_user0 . '&cpanel_jsonapi_module=Email&cpanel_jsonapi_func=passwdpop&cpanel_jsonapi_apiversion=2&email=' . $email_user . '&password=' . $email_pass . '&domain=' . $sDomain;
            } else {
                $query = 'https://' . $cpanel_host . ':2083/execute/Email/passwd_pop?email=' . $email_user . '&password=' . $email_pass . '&domain=' . $sDomain;
            }

            $curl = curl_init();
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
            curl_setopt($curl, CURLOPT_HEADER, 0);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
            $header[0] = 'Authorization: Basic ' . base64_encode($cpanel_user . ':' . $cpanel_pass) . "\n\r";
            curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
            curl_setopt($curl, CURLOPT_URL, $query);
            $result = curl_exec($curl);
            if ($result === false) {
                \Aurora\System\Api::Log('curl_exec threw error "' . curl_error($curl) . '" for ' . $query);
                curl_close($curl);
                throw new ApiException(Notifications::CanNotChangePassword);
            } else {
                curl_close($curl);
                \Aurora\System\Api::Log('..:: QUERY ::.. ' . $query);
                $json_res = json_decode($result, true);
                \Aurora\System\Api::Log('..:: RESULT ::.. ' . $result);
                if ((isset($json_res['errors'])) && ($json_res['errors'] !== null)) {
                    $sErrorText = is_string($json_res['errors']) ? $json_res['errors'] : (is_array($json_res['errors']) && isset($json_res['errors'][0]) ? $json_res['errors'][0] : '');
                    throw new ApiException(Notifications::CanNotChangePassword, null, $sErrorText);
                } else {
                    $bResult = true;
                }
            }
        }

        return $bResult;
    }

    /**
     * Obtains forwarder from cPanel.
     * @param string $sDomain
     * @param string $sEmail
     * @return array
     */
    protected function getForwarder($sDomain, $sEmail, $iTenantId = 0)
    {
        $aResult = [];

        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel && $sDomain && $sEmail) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'list_forwarders',
                [
                    'domain' => $sDomain,
                    'regex' => '^' . $sEmail . '$'
                ]
            );
            $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
            $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
            if ($aParseResult
                && isset($aParseResult['Data'])
                && is_array($aParseResult['Data'])
                && count($aParseResult['Data']) > 0
            ) {
                foreach ($aParseResult['Data'] as $oData) {
                    if ($oData->forward !== '|' . $sForwardScriptPath) {
                        $aResult = [
                            'Email' => $oData->forward
                        ];
                        break;
                    }
                }
            }
        }

        return $aResult;
    }

    /**
     * Deletes forwarder from cPanel.
     * @param string $sAddress
     * @param string $sForwarder
     * @param int $iTenantId
     * @return boolean
     */
    protected function deleteForwarder($sAddress, $sForwarder, $iTenantId = 0)
    {
        $oCpanel = $this->getCpanel($iTenantId);

        if ($oCpanel && $sAddress && $sForwarder) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'delete_forwarder',
                [
                    'address' => $sAddress,
                    'forwarder' => $sForwarder
                ]
            );
            self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
            return true;
        }

        return false;
    }

    /**
     * Creates forwarder on cPanel.
     * @param string $sDomain
     * @param string $sEmail
     * @param string $sForwardEmail
     * @param int $iTenantId
     * @return boolean
     */
    protected function createForwarder($sDomain, $sEmail, $sForwardEmail, $iTenantId = 0)
    {
        $oCpanel = $this->getCpanel($iTenantId);

        if ($oCpanel && $sDomain && $sEmail && $sForwardEmail) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'add_forwarder',
                [
                    'domain' => $sDomain,
                    'email' => $sEmail,
                    'fwdopt' => 'fwd',
                    'fwdemail' => $sForwardEmail
                ]
            );
            self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
            return true;
        }

        return false;
    }

    /**
     * Obtains domain forwarders.
     * @param string $sEmail
     * @param string $sDomain
     * @param int $iTenantId
     * @return array
     */
    protected function getDomainForwarders($sEmail, $sDomain, $iTenantId = 0)
    {
        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel && $sDomain && $sEmail) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'list_forwarders',
                [
                    'domain' => $sDomain
                ]
            );
            $aResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
            if (is_array($aResult) && isset($aResult['Data'])) {
                return $aResult['Data'];
            }
        }

        return [];
    }

    /**
     * Obtains autoresponder on cPanel.
     * @param string $sEmail
     * @return array|boolean
     */
    protected function getAutoresponder($sEmail, $iTenantId = 0)
    {
        $mResult = false;

        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel && $sEmail) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'get_auto_responder',
                [
                    'email' => $sEmail
                ]
            );
            $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
            if ($aParseResult
                && isset($aParseResult['Data'])
                && isset($aParseResult['Data']->subject)
            ) {
                if (isset($aParseResult['Data']->stop) && $aParseResult['Data']->stop !== null && $aParseResult['Data']->stop < time()) {
                    $bEnable = false;
                } else {
                    $bEnable = true;
                }
                $mResult = [
                    'Subject' => $aParseResult['Data']->subject,
                    'Message' => isset($aParseResult['Data']->body) ? $aParseResult['Data']->body : '',
                    'Enable' => $bEnable
                ];
            }
        }

        return $mResult;
    }

    /**
     * Updates autoresponder on cPanel.
     * @param string $sDomain
     * @param string $sEmail
     * @param string $sSubject
     * @param string $sMessage
     * @param boolean $bEnable
     * @return array
     */
    protected function updateAutoresponder($sDomain, $sEmail, $sSubject, $sMessage, $bEnable, $iTenantId = 0)
    {
        $aResult = [
            'Status' => false
        ];

        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel && $sDomain && $sEmail && $sSubject && $sMessage) {
            $iStartTime = 0;
            $iStopTime = 0;
            if (!$bEnable) {
                $iStopTime = time();
                $iStartTime = $iStopTime - 1;
            }
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'add_auto_responder',
                [
                    'email' => $sEmail,
                    'from' => '',
                    'subject' => $sSubject,
                    'body' => $sMessage,
                    'domain' => $sDomain,
                    'is_html' => 0,
                    'interval' => 8,
                    'start' => $iStartTime,
                    'stop' => $iStopTime
                ]
            );
            $aResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
        }

        return $aResult;
    }

    /**
     * Deletes autoresponder from cPanel.
     * @param string $sEmail
     */
    protected function deleteAutoresponder($sEmail, $iTenantId = 0)
    {
        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel && $sEmail) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'delete_auto_responder',
                [
                    'email'	=> $sEmail,
                ]
            );
            self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
        }
    }

    /**
     * Obtains filters from cPanel.
     * @param object $oAccount
     * @return array
     */
    protected function getFilters($oAccount, $iTenantId = 0)
    {
        $aFilters = [];

        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel && $oAccount) {
            $sCpanelResponse = $this->executeCpanelAction(
                $oCpanel,
                'Email',
                'list_filters',
                [
                    'account' => $oAccount->Email
                ]
            );
            $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
            if ($aParseResult && isset($aParseResult['Data'])) {
                $aFilters = self::convertCPanelFIltersToWebmailFIlters($aParseResult['Data'], $oAccount);
            }
        }

        return $aFilters;
    }

    /**
     * Removes filters supported by the system from cPanel.
     * @param object $oAccount
     * @return boolean
     */
    protected function removeSupportedFilters($oAccount, $iTenantId = 0)
    {
        $bResult = false;

        if ($oAccount) {
            $aFilters = $this->getFilters($oAccount, $iTenantId);
            if (is_array($aFilters)) {
                if (count($aFilters) === 0) {
                    $bResult = true;
                } else {
                    $aSuportedFilterNames = array_map(function ($aFilter) {
                        return $aFilter['Filtername'];
                    }, $aFilters);

                    $oCpanel = $this->getCpanel($iTenantId);
                    if ($oCpanel) {
                        $bDelResult = true;
                        foreach ($aSuportedFilterNames as $sSuportedFilterName) {
                            $sCpanelResponse = $this->executeCpanelAction(
                                $oCpanel,
                                'Email',
                                'delete_filter',
                                [
                                    'account' => $oAccount->Email,
                                    'filtername' => $sSuportedFilterName
                                ]
                            );
                            $aDelResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
                            if (!$aDelResult['Status']) {
                                $bDelResult = false;
                                break;
                            }
                        }
                        $bResult = $bDelResult;
                    }
                }
            }
        }

        return $bResult;
    }

    /**
     * Obtains namespace for mail account from IMAP.
     * @staticvar object $oNamespace
     * @param object $oAccount
     * @return object
     */
    protected static function getImapNamespace($oAccount)
    {
        static $oNamespace = null;

        if ($oNamespace === null) {
            $oNamespace = MailModule::getInstance()->getMailManager()->_getImapClient($oAccount)->GetNamespace();
        }

        return $oNamespace;
    }

    /**
     * Parses response from cPanel.
     * @param string $sResponse
     * @param boolean $bAllowException
     * @return array
     * @throws \Exception
     */
    protected static function parseResponse($sResponse, $bAllowException = true)
    {
        $aResult = [
            'Status' => false
        ];

        $oResult = \json_decode($sResponse);

        if ($oResult
            && isset($oResult->result)
            && isset($oResult->result->status)
            && $oResult->result->status === 1
            && (isset($oResult->result->data)
                || empty($oResult->result->data))
        ) {
            $aResult = [
                'Status' => true,
                'Data' => $oResult->result->data ?? null
            ];
        } elseif ($oResult && isset($oResult->error)) {
            $aResult = [
                'Status' => false,
                'Error' => $oResult->error
            ];
        } elseif ($oResult && isset($oResult->result)
            && isset($oResult->result->errors) && !empty($oResult->result->errors)
            && isset($oResult->result->errors[0])) {
            $aResult = [
                'Status' => false,
                'Error' => $oResult->result->errors[0]
            ];
        } else {
            $aResult = [
                'Status' => false,
                'Error' => $sResponse
            ];
        }

        if ($bAllowException && $aResult['Status'] === false) {
            throw new \Exception(trim($aResult['Error'], ' ()'));
        }

        return $aResult;
    }

    /**
     * Converts cPanel filters to webmail filters.
     * @param array $aCPanelFilters
     * @param object $oAccount
     * @return array
     */
    protected static function convertCPanelFIltersToWebmailFIlters($aCPanelFilters, $oAccount)
    {
        $aResult = [];

        foreach ($aCPanelFilters as $oCPanelFilter) {
            $iAction = null;
            $iCondition = null;
            $iField = null;

            if ($oCPanelFilter->actions[0]->action === 'save') {
                if ($oCPanelFilter->actions[0]->dest === '/dev/null') {
                    $iAction = \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately;
                } else {
                    $iAction = \Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder;
                }
            }

            $sDestEmail = "";
            if ($oCPanelFilter->actions[0]->action === 'deliver') {
                $sDestEmail = $oCPanelFilter->actions[0]->dest;
                $iAction = \Aurora\Modules\Mail\Enums\FilterAction::Redirect;
            }

            switch ($oCPanelFilter->rules[0]->match) {
                case 'contains':
                    $iCondition = \Aurora\Modules\Mail\Enums\FilterCondition::ContainSubstring;
                    break;
                case 'does not contain':
                    $iCondition = \Aurora\Modules\Mail\Enums\FilterCondition::NotContainSubstring;
                    break;
                case 'is':
                    $iCondition = \Aurora\Modules\Mail\Enums\FilterCondition::ContainExactPhrase;
                    break;
            }

            switch ($oCPanelFilter->rules[0]->part) {
                case '$header_from:':
                    $iField = \Aurora\Modules\Mail\Enums\FilterFields::From;
                    break;
                case '$header_to:':
                    $iField = \Aurora\Modules\Mail\Enums\FilterFields::To;
                    break;
                case '$header_subject:':
                    $iField = \Aurora\Modules\Mail\Enums\FilterFields::Subject;
                    break;
            }
            $oNamespace = self::getImapNamespace($oAccount);

            $sNamespace = \str_replace($oNamespace->GetPersonalNamespaceDelimiter(), '', $oNamespace->GetPersonalNamespace());

            $aFolderNameParts = \explode('/', $oCPanelFilter->actions[0]->dest);
            $sFolderFullName = '';
            if (\count($aFolderNameParts) > 1 && $aFolderNameParts[\count($aFolderNameParts) - 1] !== $sNamespace) {
                $sFolderFullName = $sNamespace . $aFolderNameParts[\count($aFolderNameParts) - 1];
            }

            if (isset($iAction) && isset($iCondition) && isset($iField)
                && (!empty($sFolderFullName) || $iAction === \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately || $iAction === \Aurora\Modules\Mail\Enums\FilterAction::Redirect)
            ) {
                $aResult[] = [
                    'Action' => $iAction,
                    'Condition' => $iCondition,
                    'Enable' => (bool) $oCPanelFilter->enabled,
                    'Field' => $iField,
                    'Filter' => $oCPanelFilter->rules[0]->val,
                    'FolderFullName' => $iAction === \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately ? '' : $sFolderFullName,
                    'Filtername' => $oCPanelFilter->filtername,
                    'Email' => $sDestEmail
                ];
            }
        }

        return $aResult;
    }

    /**
     * Converts webmail filters to cPanel filters.
     * @param array $aWebmailFilter
     * @param object $oAccount
     * @return array
     */
    protected static function convertWebmailFIlterToCPanelFIlter($aWebmailFilter, $oAccount)
    {
        $sAction = '';
        $sPart = '';
        $sMatch = '';
        $oNamespace = self::getImapNamespace($oAccount);
        $sNamespace = \str_replace($oNamespace->GetPersonalNamespaceDelimiter(), '', $oNamespace->GetPersonalNamespace());
        $sDest = \str_replace($sNamespace, '/', $aWebmailFilter["FolderFullName"]);

        switch ($aWebmailFilter["Action"]) {
            case \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately:
                $sDest = '/dev/null';
                // no break
            case \Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder:
                $sAction = 'save';
                break;
            case \Aurora\Modules\Mail\Enums\FilterAction::Redirect:
                $sAction = 'deliver';
                $sDest = isset($aWebmailFilter['Email']) ? $aWebmailFilter['Email'] : $sDest;
                break;
        }

        switch ($aWebmailFilter["Condition"]) {
            case \Aurora\Modules\Mail\Enums\FilterCondition::ContainSubstring:
                $sMatch = 'contains';
                break;
            case \Aurora\Modules\Mail\Enums\FilterCondition::NotContainSubstring:
                $sMatch = 'does not contain';
                break;
            case \Aurora\Modules\Mail\Enums\FilterCondition::ContainExactPhrase:
                $sMatch = 'is';
                break;
        }

        switch ($aWebmailFilter["Field"]) {
            case \Aurora\Modules\Mail\Enums\FilterFields::From:
                $sPart = '$header_from:';
                break;
            case \Aurora\Modules\Mail\Enums\FilterFields::To:
                $sPart = '$header_to:';
                break;
            case \Aurora\Modules\Mail\Enums\FilterFields::Subject:
                $sPart = '$header_subject:';
                break;
        }

        return [
            'filtername' => \uniqid(),
            'account' => $oAccount->Email,
            'action1' => $sAction,
            'dest1' => $sDest,
            'part1' => $sPart,
            'match1' => $sMatch,
            'val1' => $aWebmailFilter['Filter'],
            'opt1' => 'or',
        ];
    }

    /**
     * Obtains list of module settings for authenticated user.
     *
     * @param int|null $TenantId Tenant ID
     *
     * @return array
     */
    public function GetSettings($TenantId = null)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $oSettings = $this->oModuleSettings;
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User) {
            if (empty($TenantId)) {
                if ($oAuthenticatedUser->isNormalOrTenant()) {
                    $aResult = ['AllowAliases' => $oSettings->AllowAliases];
                    if ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin) {
                        $aResult['AllowCreateDeleteAccountOnCpanel'] = $oSettings->AllowCreateDeleteAccountOnCpanel;
                    }
                    return $aResult;
                } elseif ($oAuthenticatedUser->isAdmin()) {
                    return [
                        'CpanelHost' => $oSettings->CpanelHost,
                        'CpanelPort' => $oSettings->CpanelPort,
                        'CpanelUser' => $oSettings->CpanelUser,
                        'CpanelHasPassword' => $oSettings->CpanelPassword !== '',
                        'AllowAliases' => $oSettings->AllowAliases,
                        'AllowCreateDeleteAccountOnCpanel' => $oSettings->AllowCreateDeleteAccountOnCpanel,
                    ];
                }
            } else {
                \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
                $oTenant = \Aurora\System\Api::getTenantById($TenantId);

                if ($oTenant && ($oAuthenticatedUser->isAdmin() || ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oAuthenticatedUser->IdTenant === $oTenant->Id))) {
                    return [
                        'CpanelHost' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelHost', ''),
                        'CpanelPort' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelPort', ''),
                        'CpanelUser' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelUser', ''),
                        'CpanelHasPassword' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelPassword', '') !== '',
                    ];
                }
            }
        }

        return [];
    }

    /**
     * Updates module's settings - saves them to config.json file or to user settings in db.
     * @param string $CpanelHost
     * @param string $CpanelPort
     * @param string $CpanelUser
     * @param int $CpanelPassword
     * @param int|null $TenantId
     *
     * @return boolean
     */
    public function UpdateSettings($CpanelHost, $CpanelPort, $CpanelUser, $CpanelPassword, $TenantId = null)
    {
        $result = false;

        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();

        $oSettings = $this->oModuleSettings;
        if (!empty($TenantId)) {

            $oTenant = \Aurora\System\Api::getTenantById($TenantId);

            if ($oTenant && ($oAuthenticatedUser->isAdmin() || $oAuthenticatedUser->IdTenant === $oTenant->Id)) {
                $aValues = [
                    'CpanelHost' => $CpanelHost,
                    'CpanelPort' => $CpanelPort,
                    'CpanelUser' => $CpanelUser
                ];
                if ($CpanelPassword !== '') {
                    $aValues['CpanelPassword'] = $CpanelPassword;
                } else {
                    $aValues['CpanelPassword'] =  $oSettings->GetTenantValue($oTenant->Name, 'CpanelPassword');
                }
                $result = $oSettings->SaveTenantSettings($oTenant->Name, $aValues);
            }
        } else {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);

            $oSettings->CpanelHost = $CpanelHost;
            $oSettings->CpanelPort = $CpanelPort;
            $oSettings->CpanelUser = $CpanelUser;
            if ($CpanelPassword !== '') {
                $oSettings->CpanelPassword = $CpanelPassword;
            }
            $result = $oSettings->Save();
        }

        return $result;
    }

    /**
     * Obtains all aliases for specified user.
     * @param int $UserId User identifier.
     * @return array|boolean
     */
    public function GetAliases($UserId)
    {
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();

        $oUser = \Aurora\Api::getUserById($UserId);
        $bUserFound = $oUser instanceof \Aurora\Modules\Core\Models\User;
        if ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::NormalUser && $oUser->Id === $oAuthenticatedUser->Id) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        } elseif ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
        } else {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
        }
        $oAccount = MailModule::Decorator()->GetAccountByEmail($oUser->PublicId, $oUser->Id);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
            $aForwardersFromEmail = [];
            $sEmail = $oAccount->Email;
            $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);

            $aAliases = $this->getManager('Aliases')->getAliasesByUserId($oUser->Id);
            //is Server Supported
            $oServer = MailModule::getInstance()->getServersManager()->getServer($oAccount->ServerId);
            $bSupported = in_array($oServer->IncomingServer, $this->oModuleSettings->SupportedServers);
            if ($bSupported) {
                $aServerDomainsByTenant = MailModule::Decorator()->GetServerDomains($oAccount->ServerId, $oUser->IdTenant);
                //Get forwarders for all supported domains
                $aForwarders = [];
                foreach ($aServerDomainsByTenant as $sServerDomain) {
                    $aForwarders = array_merge($aForwarders, $this->getDomainForwarders($sEmail, $sServerDomain, $oUser->IdTenant));
                }

                //filter forvarders
                $aFilteredForwarders = $this->getAliasesFromForwarders($aForwarders, $oUser->IdTenant);
                foreach ($aFilteredForwarders as $oForwarder) {
                    $sFromEmail = $oForwarder->dest;
                    $sToEmail = $oForwarder->forward;
                    if ($sToEmail === $sEmail) {
                        $aForwardersFromEmail[] = $sFromEmail;
                    }
                }
                $aAliasesEmail = $aAliases->map(function ($oAlias) {
                    return $oAlias->Email;
                })->toArray();

                foreach ($aForwardersFromEmail as $sForwarderFromEmail) {
                    if (!in_array($sForwarderFromEmail, $aAliasesEmail)) {
                        //Check if an alias exists
                        $aAliasesCheck = $this->getManager('Aliases')->getAliases(0, 0, Models\Alias::where('Email', $sForwarderFromEmail));
                        if (empty($aAliasesCheck)) {
                            //create alias if doesn't exists
                            $oAlias = new \Aurora\Modules\CpanelIntegrator\Models\Alias();
                            $oAlias->IdUser = $oUser->Id;
                            $oAlias->IdAccount = $oAccount->Id;
                            $oAlias->Email = $sForwarderFromEmail;
                            $oAlias->ForwardTo = $oAccount->Email;
                            $this->getManager('Aliases')->createAlias($oAlias);
                        }
                    }
                }
                foreach ($aAliases as $oAlias) {
                    if (!in_array($oAlias->Email, $aForwardersFromEmail)) {
                        $this->getManager('Aliases')->deleteAlias($oAlias);
                    }
                }
                $aAliases = $this->getManager('Aliases')->getAliasesByUserId($oUser->Id);
            }

            return [
                'Domain'		=> $sDomain,
                'Aliases'		=> $aForwardersFromEmail,
                'ObjAliases'	=> $aAliases->all()
            ];
        }

        return false;
    }

    /**
     * Creates new alias with specified name and domain.
     * @param int $UserId User identifier.
     * @param string $AliasName Alias name.
     * @param string $AliasDomain Alias domain.
     * @return boolean|int
     */
    public function AddNewAlias($UserId, $AliasName, $AliasDomain)
    {
        $AliasName = strtolower($AliasName); // cPanel creates an alias with a lowercase name.
        $mResult = false;
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();

        $oUser = \Aurora\Api::getUserById($UserId);
        $bUserFound = $oUser instanceof \Aurora\Modules\Core\Models\User;
        if ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::NormalUser && $UserId === $oAuthenticatedUser->Id) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        } elseif ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
        } else {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
        }

        $oMailDecorator = MailModule::Decorator();
        $oAccount = $bUserFound && $oMailDecorator ? $oMailDecorator->GetAccountByEmail($oUser->PublicId, $oUser->Id) : null;
        $aServerDomainsByTenant = $oMailDecorator ? $oMailDecorator->GetServerDomains($oAccount->ServerId, $oUser->IdTenant) : [];
        //AliasDomain must be in the tenant’s domain list
        if (!in_array($AliasDomain, $aServerDomainsByTenant)) {
            throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::DomainOutsideTenant);
        }
        //Checking if an alias matches an existing account
        $oCpanel = $this->getCpanel($oUser->IdTenant);
        if ($oCpanel) {
            $sCpanelResponse = $this->executeCpanelAction($oCpanel, 'Email', 'list_pops', []);
            $aParseResult = self::parseResponse($sCpanelResponse);
            if ($aParseResult && isset($aParseResult['Data'])) {
                $aEmailAccountsOnCPanel = array_map(function ($oAccount) {
                    return $oAccount->email;
                }, $aParseResult['Data']);
                if (in_array($AliasName . '@' . $AliasDomain, $aEmailAccountsOnCPanel)) {
                    throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::AliaMatchesExistingEmail);
                }
            }
        }
        //Check if an alias exists on CPanel
        $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
        $aResult = $this->getForwarder($AliasDomain, $AliasName . '@' . $AliasDomain, $oUser->IdTenant);
        $bAliasExists = is_array($aResult) && isset($aResult['Email']) && !empty($aResult['Email']);
        if ($bAliasExists) {
            throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::AliasAlreadyExists);
        }
        //Check if an alias exists
        $aAliases = $this->getManager('Aliases')->getAliases(0, 0, Models\Alias::where('Email', $AliasName . '@' . $AliasDomain));
        if ($aAliases->count() > 0) {
            throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::AliasAlreadyExists);
        }

        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)) {
            $sEmail = $oAccount->Email;
            $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
            $aServerDomainsByTenant = $oMailDecorator ? $oMailDecorator->GetServerDomains($oAccount->ServerId, $oUser->IdTenant) : [];
            //Get forwarders for all supported domains
            $aForwarders = [];
            foreach ($aServerDomainsByTenant as $sServerDomain) {
                $aForwarders = array_merge($aForwarders, $this->getDomainForwarders($sEmail, $sServerDomain, $oUser->IdTenant));
            }
            $aDomainAliases = $this->getAliasesFromForwarders($aForwarders, $oUser->IdTenant);
            $aUserAliases = [];
            foreach ($aDomainAliases as $oAlias) {
                $sToEmail = $oAlias->forward;
                if ($sToEmail === $sEmail) {
                    $aUserAliases[] = $oAlias;
                }
            }
            $aArgs = [
                'TenantId' => $oUser->IdTenant,
                'DomainAliases' => $aDomainAliases,
                'UserAliases' => $aUserAliases,
                'AliasName' => $AliasName,
                'AliasDomain' => $AliasDomain,
                'ToEmail' => $oAccount->Email,
                'UserId' => $oUser->Id
            ];
            $this->broadcastEvent(
                'CreateAlias::before',
                $aArgs
            );
            $bCreateForwardewResult = $this->createForwarder($AliasDomain, $AliasName . '@' . $AliasDomain, $oAccount->Email, $oUser->IdTenant);
            if ($bCreateForwardewResult) {
                //create Alias
                $oAlias = new \Aurora\Modules\CpanelIntegrator\Models\Alias();
                $oAlias->IdUser = $oUser->Id;
                $oAlias->IdAccount = $oAccount->Id;
                $oAlias->Email = $AliasName . '@' . $AliasDomain;
                $oAlias->ForwardTo = $oAccount->Email;
                $mResult = $this->getManager('Aliases')->createAlias($oAlias);
            }
        }

        return $mResult;
    }

    /**
     * Update existing Alias
     *
     * @param int $UserId
     * @param int $AccountID
     * @param string $FriendlyName
     * @param int $EntityId
     * @return bool
     * @throws \Aurora\System\Exceptions\ApiException
     */
    public function UpdateAlias($UserId, $AccountID, $FriendlyName, $EntityId)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $bResult = false;
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAccount = MailModule::Decorator()->GetAccount($AccountID);
        if (!$oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount || $oAccount->IdUser !== $oUser->Id) {
            throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
        }
        $oAlias = $this->getManager('Aliases')->getAlias((int) $EntityId);
        if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias
            && $oAlias->IdUser === $oUser->Id
        ) {
            $oAlias->FriendlyName = $FriendlyName;
            $bResult = $this->getManager('Aliases')->updateAlias($oAlias);
        }

        return $bResult;
    }

    /**
     * Deletes aliases with specified emails.
     * @param int $UserId User identifier
     * @param array $Aliases Aliases emails.
     * @return boolean
     */
    public function DeleteAliases($UserId, $Aliases)
    {
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();

        $oUser = \Aurora\Api::getUserById($UserId);
        $bUserFound = $oUser instanceof \Aurora\Modules\Core\Models\User;

        if ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::NormalUser && $oUser->Id === $oAuthenticatedUser->Id) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        } elseif ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
        } else {
            \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
        }

        $bResult = false;
        $oAccount = $bUserFound ? MailModule::Decorator()->GetAccountByEmail($oUser->PublicId, $oUser->Id) : null;
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
                && $this->isAccountServerSupported($oAccount)) {
            foreach ($Aliases as $sAlias) {
                preg_match('/(.+)@(.+)$/', $sAlias, $aMatches);
                $AliasName = isset($aMatches[1]) ? $aMatches[1] : '';
                $AliasDomain = isset($aMatches[2]) ? $aMatches[2] : '';
                $bResult = $this->deleteForwarder($AliasName . '@' . $AliasDomain, $oAccount->Email, $oUser->IdTenant);
                if ($bResult) {
                    $oAlias = $this->getManager('Aliases')->getUserAliasByEmail($oUser->Id, $AliasName . '@' . $AliasDomain);
                    if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias) {
                        $this->getManager('Aliases')->deleteAlias($oAlias);
                    }
                }
            }
        }

        return $bResult;
    }

    public function UpdateSignature($UserId, $AliasId = null, $UseSignature = null, $Signature = null)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $bResult = false;
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser->Id === $UserId) {
            $oAlias = $this->getManager('Aliases')->getAlias((int) $AliasId);
            if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias && $oAlias->IdUser === $oUser->Id) {
                $oAlias->UseSignature = $UseSignature;
                $oAlias->Signature = $Signature;
                $bResult = $this->getManager('Aliases')->updateAlias($oAlias);
            }
        }

        return $bResult;
    }

    protected function getAliasesFromForwarders($aForwarders, $iTenantId = 0)
    {
        $aResult = [];
        $oCpanel = $this->getCpanel($iTenantId);
        if ($oCpanel) {
            $sCpanelResponse = $this->executeCpanelAction($oCpanel, 'Email', 'list_pops', []);
            $aParseResult = self::parseResponse($sCpanelResponse);
            if ($aParseResult && isset($aParseResult['Data'])) {
                $aEmailAccountsOnCPanel = array_map(function ($oAccount) {
                    return $oAccount->email;
                }, $aParseResult['Data']);
                foreach ($aForwarders as &$oForwarder) {
                    //if 'dest' is the email address of the real account, it is not an alias
                    if (!in_array($oForwarder->dest, $aEmailAccountsOnCPanel)) {
                        $aResult[] = $oForwarder;
                    }
                }
            }
        }

        return $aResult;
    }

    protected function isAliasExists($sEmail, $iTenantId = 0)
    {
        //Check if an alias exists on CPanel
        $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
        $aResult = $this->getForwarder($sDomain, $sEmail, $iTenantId);
        $bAliasExists = is_array($aResult) && isset($aResult['Email']) && !empty($aResult['Email']);

        return $bAliasExists;
    }

    public function GetScriptForward($AccountID)
    {
        $aResult = [];

        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($AccountID);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
            // check if account belongs to authenticated user
            && ($oAccount->IdUser === $oAuthenticatedUser->Id
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
                $oCpanel = null;
                $oUser = \Aurora\Api::getUserById($oAccount->IdUser);
                if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                    $oCpanel = $this->getCpanel($oUser->IdTenant);
                    $sEmail = $oAccount->Email;
                    $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oUser->PublicId);
                    $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
                    if ($oCpanel && $sDomain && $sEmail) {
                        $sCpanelResponse = $this->executeCpanelAction(
                            $oCpanel,
                            'Email',
                            'list_forwarders',
                            [
                                'domain' => $sDomain,
                                'regex' => '^' . $sEmail . '$'
                            ]
                        );
                        $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
                        if ($aParseResult
                            && isset($aParseResult['Data'])
                            && is_array($aParseResult['Data'])
                            && count($aParseResult['Data']) > 0
                        ) {
                            foreach ($aParseResult['Data'] as $oData) {
                                if ($oData->forward === '|' . $sForwardScriptPath) {
                                    $aResult = [
                                        'Email' => $oData->forward
                                    ];
                                    break;
                                }
                            }
                        }
                    } else {
                        throw new ApiException(Notifications::InvalidInputParameter);
                    }
                }
            }
        }

        return $aResult;
    }

    public function CreateScriptForward($AccountID)
    {
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($AccountID);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
            // check if account belongs to authenticated user
            && ($oAccount->IdUser === $oAuthenticatedUser->Id
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
                $oCpanel = null;
                $oUser = \Aurora\Api::getUserById($oAccount->IdUser);
                if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                    $oCpanel = $this->getCpanel($oUser->IdTenant);
                    $sEmail = $oAccount->Email;
                    $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oUser->PublicId);
                    $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';

                    if ($oCpanel && $sDomain && $sEmail) {
                        $sCpanelResponse = $this->executeCpanelAction(
                            $oCpanel,
                            'Email',
                            'add_forwarder',
                            [
                                'domain' => $sDomain,
                                'email' => $sEmail,
                                'fwdopt' => 'pipe',
                                'pipefwd' => $sForwardScriptPath
                            ]
                        );
                        self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
                        return true;
                    } else {
                        throw new ApiException(Notifications::InvalidInputParameter);
                    }
                }
            }
        }

        return false;
    }

    public function RemoveScriptForward($AccountID)
    {
        $bResult = false;
        $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
        $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($AccountID);
        if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
            if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
            // check if account belongs to authenticated user
            && ($oAccount->IdUser === $oAuthenticatedUser->Id
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
            || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
                $sFromEmail = $oAccount->Email;
                $oUser = \Aurora\Api::getUserById($oAccount->IdUser);
                if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                    $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
                    try {
                        $this->deleteForwarder($sFromEmail, '|' . $sForwardScriptPath, $oUser->IdTenant);
                        $bResult = true;
                    } catch (\Exception $oEx) {
                        $bResult = false;
                    }
                }
            }
        }

        return $bResult;
    }
}