1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\Modules\CpanelIntegrator;
9:
10: use Aurora\Modules\Core\Classes\Tenant;
11: use Aurora\Modules\Core\Module as CoreModule;
12: use Aurora\Modules\Mail\Module as MailModule;
13: use Aurora\Modules\MailDomains\Classes\Domain;
14: use Aurora\System\Exceptions\ApiException;
15:
16: /**
17: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
18: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
19: * @copyright Copyright (c) 2023, Afterlogic Corp.
20: *
21: * @property Settings $oModuleSettings
22: *
23: * @package Modules
24: */
25: class Module extends \Aurora\System\Module\AbstractModule
26: {
27: public const UAPI = '3';
28: private $oCpanel = [];
29:
30: protected $aManagers = [
31: 'Aliases' => null
32: ];
33:
34: public $oMailModule = null;
35:
36: /**
37: * @return Module
38: */
39: public static function getInstance()
40: {
41: return parent::getInstance();
42: }
43:
44: /**
45: * @return Module
46: */
47: public static function Decorator()
48: {
49: return parent::Decorator();
50: }
51:
52: /**
53: * @return Settings
54: */
55: public function getModuleSettings()
56: {
57: return $this->oModuleSettings;
58: }
59:
60: public function init()
61: {
62: $this->aErrors = [
63: Enums\ErrorCodes::DomainOutsideTenant => $this->i18N('ERROR_DOMAIN_OUTSIDE_TENANT'),
64: Enums\ErrorCodes::AliaMatchesExistingEmail => $this->i18N('ERROR_CREATE_ALIAS'),
65: Enums\ErrorCodes::AliasAlreadyExists => $this->i18N('ERROR_ALIAS_ALREADY_EXISTS'),
66: ];
67: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
68: if ($this->oModuleSettings->AllowCreateDeleteAccountOnCpanel && $oAuthenticatedUser &&
69: ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
70: // Subscription shouldn't work for Anonymous because Signup subscription will work
71: // Subscription shouldn't work for Normal user because CPanel account should be created only for first user account
72: $this->subscribeEvent('Mail::CreateAccount::before', array($this, 'onBeforeCreateAccount'));
73: $this->subscribeEvent('Mail::BeforeDeleteAccount', array($this, 'onBeforeDeleteAccount'));
74: }
75: $this->subscribeEvent('MailSignup::Signup::before', [$this, 'onAfterSignup']);
76: $this->subscribeEvent('Mail::Account::ToResponseArray', array($this, 'onMailAccountToResponseArray'));
77: $this->subscribeEvent('ChangeAccountPassword', array($this, 'onChangeAccountPassword'));
78: $this->subscribeEvent('Mail::UpdateForward::before', array($this, 'onBeforeUpdateForward'));
79: $this->subscribeEvent('Mail::GetForward::before', array($this, 'onBeforeGetForward'));
80: $this->subscribeEvent('Mail::GetAutoresponder::before', array($this, 'onBeforeGetAutoresponder'));
81: $this->subscribeEvent('Mail::UpdateAutoresponder::before', array($this, 'onBeforeUpdateAutoresponder'));
82: $this->subscribeEvent('Mail::GetFilters::before', array($this, 'onBeforeGetFilters'));
83: $this->subscribeEvent('Mail::UpdateFilters::before', array($this, 'onBeforeUpdateFilters'));
84: $this->subscribeEvent('Mail::UpdateQuota', array($this, 'onUpdateQuota'));
85: $this->subscribeEvent('Mail::SaveMessage::before', array($this, 'onBeforeSendOrSaveMessage'));
86: $this->subscribeEvent('Mail::SendMessage::before', array($this, 'onBeforeSendOrSaveMessage'));
87:
88: $this->subscribeEvent('Mail::IsEmailAllowedForCreation::after', array($this, 'onAfterIsEmailAllowedForCreation'));
89: }
90:
91: /**
92: * @param string $sManager
93: * @return object
94: */
95: public function getManager($sManager)
96: {
97: if ($this->aManagers[$sManager] === null) {
98: $sManagerClass = Module::getNamespace() . "\\Managers\\" . $sManager;
99: $this->aManagers[$sManager] = new $sManagerClass($this);
100: }
101:
102: return $this->aManagers[$sManager];
103: }
104:
105: /**
106: * Connects to cPanel. Uses main credentials if $iTenantId has not been passed, otherwise - tenant credentials to cPanel.
107: * @param int $iTenantId
108: * @return object
109: */
110: protected function getCpanel($iTenantId = 0)
111: {
112: if (!isset($this->oCpanel[$iTenantId])) {
113: $sHost = $this->oModuleSettings->CpanelHost;
114: $sPort = $this->oModuleSettings->CpanelPort;
115: $sUser = $this->oModuleSettings->CpanelUser;
116: $sPassword = $this->oModuleSettings->CpanelPassword;
117: if ($sPassword && !\Aurora\System\Utils::IsEncryptedValue($sPassword)) {
118: $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
119: $this->Decorator()->UpdateSettings($sHost, $sPort, $sUser, \Aurora\System\Utils::EncryptValue($sPassword), null);
120: $bPrevState = \Aurora\System\Api::skipCheckUserRole($bPrevState);
121: } else {
122: $sPassword = \Aurora\System\Utils::DecryptValue($sPassword);
123: }
124:
125: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
126: if (\Aurora\System\Api::GetSettings()->GetValue('EnableMultiTenant') && $iTenantId !== 0 && $oAuthenticatedUser && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
127: $oSettings = $this->oModuleSettings;
128: $oTenant = \Aurora\System\Api::getTenantById($iTenantId);
129: $sHost = $oSettings->GetTenantValue($oTenant->Name, 'CpanelHost', '');
130: $sPort = $oSettings->GetTenantValue($oTenant->Name, 'CpanelPort', '');
131: $sUser = $oSettings->GetTenantValue($oTenant->Name, 'CpanelUser', '');
132: $sPassword = $oSettings->GetTenantValue($oTenant->Name, 'CpanelPassword', '');
133: if ($sPassword && !\Aurora\System\Utils::IsEncryptedValue($sPassword)) {
134: if ($oSettings->IsTenantSettingsExists($oTenant->Name)) {
135: $this->Decorator()->UpdateSettings($sHost, $sPort, $sUser, \Aurora\System\Utils::EncryptValue($sPassword), $iTenantId);
136: }
137: } else {
138: $sPassword = \Aurora\System\Utils::DecryptValue($sPassword);
139: }
140: }
141:
142: $this->oCpanel[$iTenantId] = new \Gufy\CpanelPhp\Cpanel([
143: 'host' => "https://" . $sHost . ":" . $sPort,
144: 'username' => $sUser,
145: 'auth_type' => 'password',
146: 'password' => $sPassword,
147: ]);
148:
149: $this->oCpanel[$iTenantId]->setTimeout(30);
150: }
151:
152: return $this->oCpanel[$iTenantId];
153: }
154:
155: /**
156: * Executes cPanel command, logs parameters and the result.
157: * @param object $oCpanel
158: * @param string $sModule
159: * @param string $sFunction
160: * @param array $aParams
161: * @return string
162: */
163: protected function executeCpanelAction($oCpanel, $sModule, $sFunction, $aParams)
164: {
165: // There are no params in log because they can contain private data (password for example)
166: \Aurora\System\Api::Log('cPanel execute action. Module ' . $sModule . '. Function ' . $sFunction);
167: $sResult = $oCpanel->execute_action(self::UAPI, $sModule, $sFunction, $oCpanel->getUsername(), $aParams);
168: \Aurora\System\Api::Log($sResult);
169: return $sResult;
170: }
171:
172: /**
173: * Creates account with credentials specified in registration form
174: *
175: * @param array $aArgs New account credentials.
176: * @param mixed $mResult Is passed by reference.
177: */
178: public function onAfterSignup($aArgs, &$mResult)
179: {
180: if (isset($aArgs['Login']) && isset($aArgs['Password']) && !empty(trim($aArgs['Password'])) && !empty(trim($aArgs['Login']))) {
181: $sLogin = trim($aArgs['Login']);
182: $sPassword = trim($aArgs['Password']);
183: $sFriendlyName = isset($aArgs['Name']) ? trim($aArgs['Name']) : '';
184: $bSignMe = isset($aArgs['SignMe']) ? (bool) $aArgs['SignMe'] : false;
185: $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
186: $iUserId = \Aurora\Modules\Core\Module::Decorator()->CreateUser(0, $sLogin);
187: $oUser = \Aurora\System\Api::getUserById((int) $iUserId);
188: $oCpanel = null;
189: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
190: try {
191: $oCpanel = $this->getCpanel($oUser->IdTenant);
192: } catch(\Exception $oException) {
193: }
194:
195: if ($oCpanel) {
196: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oUser->PublicId);
197: if (!empty($sDomain) && $this->isDomainSupported($sDomain)) {
198: $iQuota = (int) $this->oModuleSettings->UserDefaultQuotaMB;
199: try {
200: $sCpanelResponse = $this->executeCpanelAction(
201: $oCpanel,
202: 'Email',
203: 'add_pop',
204: [
205: 'email' => $sLogin,
206: 'password' => $sPassword,
207: 'quota' => $iQuota,
208: 'domain' => $sDomain
209: ]
210: );
211: $aParseResult = self::parseResponse($sCpanelResponse, false);
212: } catch(\Exception $oException) {
213: throw new ApiException(0, $oException, $oException->getMessage());
214: }
215: if (is_array($aParseResult) && isset($aParseResult['Data']) && !empty($aParseResult['Data'])) {
216: try {
217: $oAccount = \Aurora\Modules\Mail\Module::Decorator()->CreateAccount($oUser->Id, $sFriendlyName, $sLogin, $sLogin, $sPassword);
218: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount) {
219: $iTime = $bSignMe ? 0 : time();
220: $sAuthToken = \Aurora\System\Api::UserSession()->Set(
221: [
222: 'token' => 'auth',
223: 'sign-me' => $bSignMe,
224: 'id' => $oAccount->IdUser,
225: 'account' => $oAccount->Id,
226: 'account_type' => $oAccount->getName()
227: ],
228: $iTime
229: );
230: $mResult = [\Aurora\System\Application::AUTH_TOKEN_KEY => $sAuthToken];
231: }
232: } catch (\Exception $oException) {
233: if ($oException instanceof \Aurora\Modules\Mail\Exceptions\Exception &&
234: $oException->getCode() === \Aurora\Modules\Mail\Enums\ErrorCodes::CannotLoginCredentialsIncorrect) {
235: \Aurora\Modules\Core\Module::Decorator()->DeleteUser($oUser->Id);
236: }
237: throw $oException;
238: }
239: } elseif (is_array($aParseResult) && isset($aParseResult['Error'])) {
240: //If Account wasn't created - delete user
241: $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
242: \Aurora\Modules\Core\Module::Decorator()->DeleteUser($oUser->Id);
243: \Aurora\System\Api::skipCheckUserRole($bPrevState);
244: throw new \Exception($aParseResult['Error']);
245: }
246: }
247: }
248: }
249: \Aurora\System\Api::skipCheckUserRole($bPrevState);
250: }
251:
252: return true; // break subscriptions to prevent account creation in other modules
253: }
254:
255: /**
256: * Creates cPanel account before mail account will be created in the system.
257: * @param array $aArgs
258: * @param mixed $mResult
259: * @throws \Exception
260: */
261: public function onBeforeCreateAccount($aArgs, &$mResult)
262: {
263: $iUserId = $aArgs['UserId'];
264: $oUser = \Aurora\System\Api::getUserById($iUserId);
265: $sAccountEmail = $aArgs['Email'];
266: if ($this->isAliasExists($sAccountEmail, $oUser->IdTenant)) {
267: throw new \Exception($this->i18N('ERROR_EMAIL_MATCHES_ALIAS'));
268: }
269: if ($oUser instanceof \Aurora\Modules\Core\Models\User && $sAccountEmail === $oUser->PublicId) {
270: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sAccountEmail);
271: if (!empty($sDomain) && $this->isDomainSupported($sDomain)) {
272: $aTenantQuota = \Aurora\Modules\Mail\Module::Decorator()->GetEntitySpaceLimits('Tenant', $oUser->Id, $oUser->IdTenant);
273: $iQuota = $aTenantQuota ? $aTenantQuota['UserSpaceLimitMb'] : (int) $this->oModuleSettings->UserDefaultQuotaMB;
274: $oCpanel = $this->getCpanel($oUser->IdTenant);
275: $sCpanelResponse = $this->executeCpanelAction(
276: $oCpanel,
277: 'Email',
278: 'add_pop',
279: [
280: 'email' => $aArgs['IncomingLogin'],
281: 'password' => $aArgs['IncomingPassword'],
282: 'quota' => $iQuota,
283: 'domain' => $sDomain
284: ]
285: );
286: $aResult = self::parseResponse($sCpanelResponse, false);
287: if ($aResult['Status'] === false && isset($aResult['Error']) && !empty($aResult['Error']) && strrpos(strtolower($aResult['Error']), 'exists') === false) {
288: throw new \Exception($aResult['Error']);
289: }
290: }
291: }
292: }
293:
294: /**
295: * Deletes cPanel account, its aliases, forward, autoresponder and filters.
296: * @param array $aArgs
297: * @param mixed $mResult
298: */
299: public function onBeforeDeleteAccount($aArgs, &$mResult)
300: {
301: $oAccount = $aArgs['Account'];
302: $oUser = $aArgs['User'];
303: if ($oUser instanceof \Aurora\Modules\Core\Models\User
304: && $oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
305: && $this->isAccountServerSupported($oAccount)
306: && $oAccount->Email === $oUser->PublicId
307: ) {
308: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
309: if ($sDomain !== '') {
310: $aAliases = self::Decorator()->GetAliases($oUser->Id);
311: if (isset($aAliases['Aliases']) && is_array($aAliases['Aliases']) && count($aAliases['Aliases']) > 0) {
312: self::Decorator()->DeleteAliases($oUser->Id, $aAliases['Aliases']);
313: }
314:
315: $aForwardArgs = [
316: 'AccountID' => $oAccount->Id,
317: 'Enable' => false,
318: 'Email' => '',
319: ];
320: $mForwardResult = false;
321: $this->onBeforeUpdateForward($aForwardArgs, $mForwardResult);
322:
323: $this->deleteAutoresponder($oAccount->Email, $oUser->IdTenant);
324:
325: //Checking if an account exists on CPanel
326: $oCpanel = $this->getCpanel($oUser->IdTenant);
327: if ($oCpanel) {
328: $sCpanelResponse = $this->executeCpanelAction(
329: $oCpanel,
330: 'Email',
331: 'list_pops',
332: ['regex' => $oAccount->Email]
333: );
334: $aParseResult = self::parseResponse($sCpanelResponse);
335: //Trying to delete an Account only if it exists on CPanel
336: if (
337: $aParseResult
338: && isset($aParseResult['Data'])
339: && is_array($aParseResult['Data'])
340: && count($aParseResult['Data']) > 0
341: && $this->oModuleSettings->AllowCreateDeleteAccountOnCpanel
342: ) {
343: $sCpanelResponse = $this->executeCpanelAction(
344: $oCpanel,
345: 'Email',
346: 'delete_pop',
347: [
348: 'email' => $oAccount->Email,
349: 'domain' => $sDomain,
350: ]
351: );
352: self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
353: }
354: }
355: }
356: }
357: }
358:
359: /**
360: * Updates mail quota on cPanel.
361: * @param array $aArgs
362: * @param mixed $mResult
363: */
364: public function onUpdateQuota($aArgs, &$mResult)
365: {
366: $iTenantId = $aArgs['TenantId'];
367: $sEmail = $aArgs['Email'];
368: $iQuota = $aArgs['QuotaMb'];
369: $oCpanel = $this->getCpanel($iTenantId);
370: $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail);
371: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
372: if ($this->isDomainSupported($sDomain)) {
373: $sCpanelResponse = $this->executeCpanelAction(
374: $oCpanel,
375: 'Email',
376: 'edit_pop_quota',
377: [
378: 'email' => $sLogin,
379: 'domain' => $sDomain,
380: 'quota' => $iQuota
381: ]
382: );
383: $mResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
384: }
385: }
386:
387: public function onBeforeSendOrSaveMessage(&$aArgs, &$mResult)
388: {
389: $oUser = \Aurora\System\Api::getAuthenticatedUser();
390: $oAlias = $this->getManager('Aliases')->getAlias((int) $aArgs['AliasID']);
391: if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias
392: && $oUser instanceof \Aurora\Modules\Core\Models\User
393: && $oAlias->IdUser === $oUser->Id
394: ) {
395: $aArgs['Alias'] = $oAlias;
396: }
397: }
398:
399: /**
400: * Adds to account response array information about if it is allowed to change the password for this account.
401: * @param array $aArguments
402: * @param mixed $mResult
403: */
404: public function onMailAccountToResponseArray($aArguments, &$mResult)
405: {
406: $oAccount = $aArguments['Account'];
407:
408: if ($oAccount && $this->isAccountServerSupported($oAccount)) {
409: if (!isset($mResult['Extend']) || !is_array($mResult['Extend'])) {
410: $mResult['Extend'] = [];
411: }
412: $mResult['Extend']['AllowChangePasswordOnMailServer'] = true;
413:
414: if (class_exists('\Aurora\Modules\Mail\Module')) {
415: $oMailModule = \Aurora\Modules\Mail\Module::getInstance();
416:
417: $mResult['AllowFilters'] = $oMailModule->oModuleSettings->AllowFilters;
418: $mResult['AllowForward'] = $oMailModule->oModuleSettings->AllowForward;
419: $mResult['AllowAutoresponder'] = $oMailModule->oModuleSettings->AllowAutoresponder;
420: }
421:
422: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($oAccount->IdUser);
423: if ($oAccount->Email === $oUser->PublicId) {
424: try {
425: $aAliases = self::Decorator()->GetAliases($oAccount->IdUser);
426: $mResult['Extend']['Aliases'] = is_array($aAliases) && isset($aAliases['Aliases']) ? $aAliases['Aliases'] : [];
427: } catch (\Exception $oException) {
428: }
429: }
430: }
431: }
432:
433: /**
434: * Tries to change password for account if it is allowed.
435: * @param array $aArguments
436: * @param mixed $mResult
437: */
438: public function onChangeAccountPassword($aArguments, &$mResult)
439: {
440: $bPasswordChanged = false;
441: $bBreakSubscriptions = false;
442:
443: $oAccount = $aArguments['Account'];
444: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
445: && $this->isAccountServerSupported($oAccount)
446: && ($oAccount->getPassword() === $aArguments['CurrentPassword'])) {
447: $bPasswordChanged = $this->changePassword($oAccount, $aArguments['NewPassword']);
448: $bBreakSubscriptions = true; // break if Cpanel plugin tries to change password in this account.
449: }
450:
451: if (is_array($mResult)) {
452: $mResult['AccountPasswordChanged'] = $mResult['AccountPasswordChanged'] || $bPasswordChanged;
453: }
454:
455: return $bBreakSubscriptions;
456: }
457:
458: /**
459: * Updates forward for specified account.
460: * @param array $aArgs
461: * @param mixed $mResult
462: * @return boolean
463: * @throws \Aurora\System\Exceptions\ApiException
464: */
465: public function onBeforeUpdateForward($aArgs, &$mResult)
466: {
467: if (!isset($aArgs['AccountID']) || !isset($aArgs['Enable']) || !isset($aArgs['Email'])
468: || $aArgs['Enable'] && (empty(trim($aArgs['Email'])) || !filter_var(trim($aArgs['Email']), FILTER_VALIDATE_EMAIL))) {
469: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
470: }
471:
472: $sToEmail = trim($aArgs['Email']);
473:
474: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
475: $oAccount = MailModule::getInstance()->GetAccount($aArgs['AccountID']);
476: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
477: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
478: // check if account belongs to authenticated user
479: && ($oAccount->IdUser === $oAuthenticatedUser->Id
480: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
481: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
482: $oCpanel = null;
483: $oUser = CoreModule::Decorator()->GetUserWithoutRoleCheck($oAccount->IdUser);
484: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
485: $oCpanel = $this->getCpanel($oUser->IdTenant);
486: }
487: if ($oCpanel) {
488: $sFromEmail = $oAccount->Email;
489: $sFromDomain = \MailSo\Base\Utils::GetDomainFromEmail($sFromEmail);
490:
491: // check if there is forwarder on cPanel
492: $aResult = $this->getForwarder($sFromDomain, $sFromEmail, $oUser->IdTenant);
493: $bForwarderExists = is_array($aResult) && isset($aResult['Email']) && !empty($aResult['Email']);
494: $sOldToEmail = $bForwarderExists ? $aResult['Email'] : '';
495: // "Enable" indicates if forwarder should exist on cPanel
496: if ($aArgs['Enable']) {
497: if ($bForwarderExists) {
498: $bSameForwarderExists = $bForwarderExists && $sOldToEmail === $sToEmail;
499: if ($bSameForwarderExists) {
500: $mResult = true;
501: } elseif ($this->deleteForwarder($sFromEmail, $sOldToEmail, $oUser->IdTenant)) {
502: $mResult = $this->createForwarder($sFromDomain, $sFromEmail, $sToEmail, $oUser->IdTenant);
503: }
504: } else {
505: $mResult = $this->createForwarder($sFromDomain, $sFromEmail, $sToEmail, $oUser->IdTenant);
506: }
507: } else {
508: if ($bForwarderExists) {
509: $mResult = $this->deleteForwarder($sFromEmail, $sOldToEmail, $oUser->IdTenant);
510: } else {
511: $mResult = true;
512: }
513: }
514: }
515: }
516:
517: return true; // breaking subscriptions to prevent update in parent module
518: }
519:
520: return false;
521: }
522:
523: /**
524: * Obtains forward data for specified account.
525: * @param array $aArgs
526: * @param mixed $mResult
527: * @return boolean
528: */
529: public function onBeforeGetForward($aArgs, &$mResult)
530: {
531: $mResult = false;
532:
533: if (isset($aArgs['AccountID'])) {
534: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
535: $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($aArgs['AccountID']);
536: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
537: && $this->isAccountServerSupported($oAccount)) {
538: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
539: // check if account belongs to authenticated user
540: && $oAccount->IdUser === $oAuthenticatedUser->Id) {
541: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
542: $aResult = $this->getForwarder($sDomain, $oAccount->Email, $oAuthenticatedUser->IdTenant);
543:
544: if (is_array($aResult)) {
545: if (isset($aResult['Email'])) {
546: $mResult = [
547: 'Enable' => true,
548: 'Email' => $aResult['Email']
549: ];
550: } else {
551: $mResult = [
552: 'Enable' => false,
553: 'Email' => ''
554: ];
555: }
556: }
557: }
558: return true; // breaking subscriptions to prevent update in parent module
559: }
560: }
561:
562: return false;
563: }
564:
565: /**
566: * Obtains autoresponder for specified account.
567: * @param array $aArgs
568: * @param mixed $mResult
569: * @return boolean
570: */
571: public function onBeforeGetAutoresponder($aArgs, &$mResult)
572: {
573: $mResult = false;
574:
575: if (isset($aArgs['AccountID'])) {
576: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
577: $oAccount = MailModule::getInstance()->GetAccount($aArgs['AccountID']);
578: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
579: && $this->isAccountServerSupported($oAccount)) {
580: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
581: // check if account belongs to authenticated user
582: && $oAccount->IdUser === $oAuthenticatedUser->Id) {
583: $mAutoresponder = $this->getAutoresponder($oAccount->Email, $oAuthenticatedUser->IdTenant);
584:
585: if (is_array($mAutoresponder) && isset($mAutoresponder['Enable'])) {
586: $mResult = [
587: 'Enable' => $mAutoresponder['Enable'],
588: 'Subject' => $mAutoresponder['Subject'],
589: 'Message' => $mAutoresponder['Message']
590: ];
591: }
592: }
593: return true; // breaking subscriptions to prevent update in parent module
594: }
595: }
596:
597: return false;
598: }
599:
600: /**
601: * Updates autoresponder data.
602: * @param array $aArgs
603: * @param mixed $mResult
604: * @return boolean
605: * @throws \Aurora\System\Exceptions\ApiException
606: */
607: public function onBeforeUpdateAutoresponder($aArgs, &$mResult)
608: {
609: if (!isset($aArgs['AccountID']) || !isset($aArgs['Enable']) || !isset($aArgs['Subject']) || !isset($aArgs['Message'])
610: || $aArgs['Enable'] && (empty(trim($aArgs['Subject'])) || empty(trim($aArgs['Message'])))) {
611: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
612: }
613:
614: $mResult = false;
615:
616: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
617: $oAccount = MailModule::getInstance()->GetAccount($aArgs['AccountID']);
618: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
619: && $this->isAccountServerSupported($oAccount)) {
620: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
621: // check if account belongs to authenticated user
622: && $oAccount->IdUser === $oAuthenticatedUser->Id) {
623: $sSubject = trim($aArgs['Subject']);
624: $sMessage = trim($aArgs['Message']);
625: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
626: $mResult = $this->updateAutoresponder($sDomain, $oAccount->Email, $sSubject, $sMessage, $aArgs['Enable'], $oAuthenticatedUser->IdTenant);
627: }
628:
629: return true; // breaking subscriptions to prevent update in parent module
630: }
631:
632: return false;
633: }
634:
635: /**
636: * Obtains filters for specified account.
637: * @param array $aArgs
638: * @param mixed $mResult
639: * @return boolean
640: */
641: public function onBeforeGetFilters($aArgs, &$mResult)
642: {
643: if (!isset($aArgs['AccountID'])) {
644: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
645: }
646:
647: $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($aArgs['AccountID']);
648: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
649: && $this->isAccountServerSupported($oAccount)) {
650: $mResult = [];
651:
652: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
653: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
654: // check if account belongs to authenticated user
655: && $oAccount->IdUser === $oAuthenticatedUser->Id) {
656: $oCpanel = $this->getCpanel($oAuthenticatedUser->IdTenant);
657: if ($oCpanel) {
658: $mResult = $this->getFilters($oAccount, $oAuthenticatedUser->IdTenant);
659: }
660: }
661:
662: return true; // breaking subscriptions to prevent update in parent module
663: }
664:
665: return false;
666: }
667:
668: /**
669: * Updates filters.
670: * @param array $aArgs
671: * @param mixed $mResult
672: * @return boolean
673: * @throws \Aurora\System\Exceptions\ApiException
674: */
675: public function onBeforeUpdateFilters($aArgs, &$mResult)
676: {
677: if (!isset($aArgs['AccountID']) || !isset($aArgs['Filters']) || !is_array($aArgs['Filters'])) {
678: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
679: }
680:
681: $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($aArgs['AccountID']);
682: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
683: && $this->isAccountServerSupported($oAccount)) {
684: $mResult = false;
685:
686: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
687: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
688: // check if account belongs to authenticated user
689: && $oAccount->IdUser === $oAuthenticatedUser->Id) {
690: $oCpanel = $this->getCpanel($oAuthenticatedUser->IdTenant);
691: if ($oCpanel && $this->removeSupportedFilters($oAccount, $oAuthenticatedUser->IdTenant)) {
692: if (count($aArgs['Filters']) === 0) {
693: $mResult = true;
694: } else {
695: foreach ($aArgs['Filters'] as $aWebmailFilter) {
696: $aFilterProperty = self::convertWebmailFIlterToCPanelFIlter($aWebmailFilter, $oAccount);
697: //create filter
698: $sCpanelResponse = $this->executeCpanelAction(
699: $oCpanel,
700: 'Email',
701: 'store_filter',
702: $aFilterProperty
703: );
704: $aCreationResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
705: //disable filter if needed
706: if (!$aCreationResult['Status']) {
707: $mResult = false;
708: break;
709: }
710: if (!$aWebmailFilter['Enable']) {
711: $sCpanelResponse = $this->executeCpanelAction(
712: $oCpanel,
713: 'Email',
714: 'disable_filter',
715: [
716: 'account' => $oAccount->Email,
717: 'filtername' => $aFilterProperty['filtername']
718: ]
719: );
720: self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
721: }
722: $mResult = true;
723: }
724: }
725: }
726: }
727:
728: return true; // breaking subscriptions to prevent update in parent module
729: }
730:
731: return false;
732: }
733:
734: public function onAfterIsEmailAllowedForCreation($aArgs, &$mResult)
735: {
736: if ($mResult && isset($aArgs['Email'])) {
737: $iTenantId = 0;
738: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserByPublicId($aArgs['Email']);
739: if ($oUser) {
740: $iTenantId = $oUser->IdTenant;
741: }
742: $mResult = !$this->isAliasExists($aArgs['Email'], $iTenantId);
743: }
744: }
745:
746: /**
747: * Checks if the CpanelIntegrator module can work with the specified domain.
748: * @param string $sDomain
749: * @return bool
750: */
751: protected function isDomainSupported($sDomain)
752: {
753: $bSupported = in_array('*', $this->oModuleSettings->SupportedServers);
754:
755: if (!$bSupported) {
756: $aGetMailServerResult = MailModule::Decorator()->GetMailServerByDomain($sDomain, /*AllowWildcardDomain*/true);
757: if (!empty($aGetMailServerResult) && isset($aGetMailServerResult['Server']) && $aGetMailServerResult['Server'] instanceof \Aurora\Modules\Mail\Models\Server) {
758: $bSupported = in_array($aGetMailServerResult['Server']->IncomingServer, $this->oModuleSettings->SupportedServers);
759: }
760: }
761:
762: return $bSupported;
763: }
764:
765: /**
766: * Checks if the CpanelIntegrator module can work with the server of the specified account.
767: * @param \Aurora\Modules\Mail\Models\MailAccount $oAccount
768: * @return bool
769: */
770: protected function isAccountServerSupported($oAccount)
771: {
772: $bSupported = in_array('*', $this->oModuleSettings->SupportedServers);
773:
774: if (!$bSupported) {
775: $oServer = $oAccount->getServer();
776: if ($oServer instanceof \Aurora\Modules\Mail\Models\Server) {
777: $bSupported = in_array($oServer->IncomingServer, $this->oModuleSettings->SupportedServers);
778: }
779: }
780:
781: return $bSupported;
782: }
783:
784: /**
785: * Tries to change password for account.
786: * @param \Aurora\Modules\Mail\Models\MailAccount $oAccount
787: * @param string $sPassword
788: * @return boolean
789: * @throws \Aurora\System\Exceptions\ApiException
790: */
791: protected function changePassword($oAccount, $sPassword)
792: {
793: $bResult = false;
794:
795: if (0 < strlen($oAccount->IncomingPassword) && $oAccount->IncomingPassword !== $sPassword) {
796: $cpanel_host = $this->oModuleSettings->CpanelHost;
797: $cpanel_user = $this->oModuleSettings->CpanelUser;
798: $cpanel_pass = $this->oModuleSettings->CpanelPassword;
799: $cpanel_user0 = null;
800: if ($cpanel_pass && !\Aurora\System\Utils::IsEncryptedValue($cpanel_pass)) {
801: $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
802: $this->Decorator()->UpdateSettings($cpanel_host, $this->oModuleSettings->CpanelPort, $cpanel_user, \Aurora\System\Utils::EncryptValue($cpanel_pass), null);
803: $bPrevState = \Aurora\System\Api::skipCheckUserRole($bPrevState);
804: } else {
805: $cpanel_pass = \Aurora\System\Utils::DecryptValue($cpanel_pass);
806: }
807:
808: $oSettings = &\Aurora\System\Api::GetSettings();
809: $oUser = \Aurora\System\Api::getUserById($oAccount->IdUser);
810: $oTenant = $oUser instanceof \Aurora\Modules\Core\Models\User ? \Aurora\System\Api::getTenantById($oUser->IdTenant) : null;
811: if ($oSettings->GetValue('EnableMultiTenant') && $oTenant instanceof \Aurora\Modules\Core\Models\Tenant) {
812: $cpanel_host = $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelHost', '');
813: $cpanel_user = $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelUser', '');
814: $cpanel_pass = $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelPassword', '');
815: if ($cpanel_pass && !\Aurora\System\Utils::IsEncryptedValue($cpanel_pass)) {
816: if ($this->oModuleSettings->IsTenantSettingsExists($oTenant->Name)) {
817: $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);
818: $this->Decorator()->UpdateSettings($cpanel_host, $this->oModuleSettings->GetTenantValue($oTenant->Name, 'CpanelPort'), $cpanel_user, \Aurora\System\Utils::EncryptValue($cpanel_pass), $oTenant->Id);
819: $bPrevState = \Aurora\System\Api::skipCheckUserRole($bPrevState);
820: }
821: } else {
822: $cpanel_pass = \Aurora\System\Utils::DecryptValue($cpanel_pass);
823: }
824: }
825:
826: $email_user = urlencode($oAccount->Email);
827: $email_pass = urlencode($sPassword);
828: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
829:
830: if ($cpanel_user == 'root') {
831: $query = 'https://' . $cpanel_host . ':2087/json-api/listaccts?api.version=1&searchtype=domain&search=' . $sDomain;
832:
833: $curl = curl_init();
834: curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
835: curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
836: curl_setopt($curl, CURLOPT_HEADER, 0);
837: curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
838: $header[0] = 'Authorization: Basic ' . base64_encode($cpanel_user . ':' . $cpanel_pass) . "\n\r";
839: curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
840: curl_setopt($curl, CURLOPT_URL, $query);
841: $result = curl_exec($curl);
842: if ($result == false) {
843: \Aurora\System\Api::Log('curl_exec threw error "' . curl_error($curl) . '" for ' . $query);
844: curl_close($curl);
845: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::UserManager_AccountNewPasswordUpdateError);
846: } else {
847: curl_close($curl);
848: \Aurora\System\Api::Log('..:: QUERY0 ::.. ' . $query);
849: $json_res = json_decode($result, true);
850: \Aurora\System\Api::Log('..:: RESULT0 ::.. ' . $result);
851: if (isset($json_res['data']['acct'][0]['user'])) {
852: $cpanel_user0 = $json_res['data']['acct'][0]['user'];
853: \Aurora\System\Api::Log('..:: USER ::.. ' . $cpanel_user0);
854: }
855: }
856: $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;
857: } else {
858: $query = 'https://' . $cpanel_host . ':2083/execute/Email/passwd_pop?email=' . $email_user . '&password=' . $email_pass . '&domain=' . $sDomain;
859: }
860:
861: $curl = curl_init();
862: curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
863: curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
864: curl_setopt($curl, CURLOPT_HEADER, 0);
865: curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
866: $header[0] = 'Authorization: Basic ' . base64_encode($cpanel_user . ':' . $cpanel_pass) . "\n\r";
867: curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
868: curl_setopt($curl, CURLOPT_URL, $query);
869: $result = curl_exec($curl);
870: if ($result === false) {
871: \Aurora\System\Api::Log('curl_exec threw error "' . curl_error($curl) . '" for ' . $query);
872: curl_close($curl);
873: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::UserManager_AccountNewPasswordUpdateError);
874: } else {
875: curl_close($curl);
876: \Aurora\System\Api::Log('..:: QUERY ::.. ' . $query);
877: $json_res = json_decode($result, true);
878: \Aurora\System\Api::Log('..:: RESULT ::.. ' . $result);
879: if ((isset($json_res['errors'])) && ($json_res['errors'] !== null)) {
880: $sErrorText = is_string($json_res['errors']) ? $json_res['errors'] : (is_array($json_res['errors']) && isset($json_res['errors'][0]) ? $json_res['errors'][0] : '');
881: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::UserManager_AccountNewPasswordUpdateError, null, $sErrorText);
882: } else {
883: $bResult = true;
884: }
885: }
886: }
887:
888: return $bResult;
889: }
890:
891: /**
892: * Obtains forwarder from cPanel.
893: * @param string $sDomain
894: * @param string $sEmail
895: * @return array
896: */
897: protected function getForwarder($sDomain, $sEmail, $iTenantId = 0)
898: {
899: $aResult = [];
900:
901: $oCpanel = $this->getCpanel($iTenantId);
902: if ($oCpanel && $sDomain && $sEmail) {
903: $sCpanelResponse = $this->executeCpanelAction(
904: $oCpanel,
905: 'Email',
906: 'list_forwarders',
907: [
908: 'domain' => $sDomain,
909: 'regex' => '^' . $sEmail . '$'
910: ]
911: );
912: $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
913: $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
914: if ($aParseResult
915: && isset($aParseResult['Data'])
916: && is_array($aParseResult['Data'])
917: && count($aParseResult['Data']) > 0
918: ) {
919: foreach ($aParseResult['Data'] as $oData) {
920: if ($oData->forward !== '|' . $sForwardScriptPath) {
921: $aResult = [
922: 'Email' => $oData->forward
923: ];
924: break;
925: }
926: }
927: }
928: }
929:
930: return $aResult;
931: }
932:
933: /**
934: * Deletes forwarder from cPanel.
935: * @param string $sAddress
936: * @param string $sForwarder
937: * @param int $iTenantId
938: * @return boolean
939: */
940: protected function deleteForwarder($sAddress, $sForwarder, $iTenantId = 0)
941: {
942: $oCpanel = $this->getCpanel($iTenantId);
943:
944: if ($oCpanel && $sAddress && $sForwarder) {
945: $sCpanelResponse = $this->executeCpanelAction(
946: $oCpanel,
947: 'Email',
948: 'delete_forwarder',
949: [
950: 'address' => $sAddress,
951: 'forwarder' => $sForwarder
952: ]
953: );
954: self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
955: return true;
956: }
957:
958: return false;
959: }
960:
961: /**
962: * Creates forwarder on cPanel.
963: * @param string $sDomain
964: * @param string $sEmail
965: * @param string $sForwardEmail
966: * @param int $iTenantId
967: * @return boolean
968: */
969: protected function createForwarder($sDomain, $sEmail, $sForwardEmail, $iTenantId = 0)
970: {
971: $oCpanel = $this->getCpanel($iTenantId);
972:
973: if ($oCpanel && $sDomain && $sEmail && $sForwardEmail) {
974: $sCpanelResponse = $this->executeCpanelAction(
975: $oCpanel,
976: 'Email',
977: 'add_forwarder',
978: [
979: 'domain' => $sDomain,
980: 'email' => $sEmail,
981: 'fwdopt' => 'fwd',
982: 'fwdemail' => $sForwardEmail
983: ]
984: );
985: self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
986: return true;
987: }
988:
989: return false;
990: }
991:
992: /**
993: * Obtains domain forwarders.
994: * @param string $sEmail
995: * @param string $sDomain
996: * @param int $iTenantId
997: * @return array
998: */
999: protected function getDomainForwarders($sEmail, $sDomain, $iTenantId = 0)
1000: {
1001: $oCpanel = $this->getCpanel($iTenantId);
1002: if ($oCpanel && $sDomain && $sEmail) {
1003: $sCpanelResponse = $this->executeCpanelAction(
1004: $oCpanel,
1005: 'Email',
1006: 'list_forwarders',
1007: [
1008: 'domain' => $sDomain
1009: ]
1010: );
1011: $aResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1012: if (is_array($aResult) && isset($aResult['Data'])) {
1013: return $aResult['Data'];
1014: }
1015: }
1016:
1017: return [];
1018: }
1019:
1020: /**
1021: * Obtains autoresponder on cPanel.
1022: * @param string $sEmail
1023: * @return array|boolean
1024: */
1025: protected function getAutoresponder($sEmail, $iTenantId = 0)
1026: {
1027: $mResult = false;
1028:
1029: $oCpanel = $this->getCpanel($iTenantId);
1030: if ($oCpanel && $sEmail) {
1031: $sCpanelResponse = $this->executeCpanelAction(
1032: $oCpanel,
1033: 'Email',
1034: 'get_auto_responder',
1035: [
1036: 'email' => $sEmail
1037: ]
1038: );
1039: $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1040: if ($aParseResult
1041: && isset($aParseResult['Data'])
1042: && isset($aParseResult['Data']->subject)
1043: ) {
1044: if (isset($aParseResult['Data']->stop) && $aParseResult['Data']->stop !== null && $aParseResult['Data']->stop < time()) {
1045: $bEnable = false;
1046: } else {
1047: $bEnable = true;
1048: }
1049: $mResult = [
1050: 'Subject' => $aParseResult['Data']->subject,
1051: 'Message' => isset($aParseResult['Data']->body) ? $aParseResult['Data']->body : '',
1052: 'Enable' => $bEnable
1053: ];
1054: }
1055: }
1056:
1057: return $mResult;
1058: }
1059:
1060: /**
1061: * Updates autoresponder on cPanel.
1062: * @param string $sDomain
1063: * @param string $sEmail
1064: * @param string $sSubject
1065: * @param string $sMessage
1066: * @param boolean $bEnable
1067: * @return array
1068: */
1069: protected function updateAutoresponder($sDomain, $sEmail, $sSubject, $sMessage, $bEnable, $iTenantId = 0)
1070: {
1071: $aResult = [
1072: 'Status' => false
1073: ];
1074:
1075: $oCpanel = $this->getCpanel($iTenantId);
1076: if ($oCpanel && $sDomain && $sEmail && $sSubject && $sMessage) {
1077: $iStartTime = 0;
1078: $iStopTime = 0;
1079: if (!$bEnable) {
1080: $iStopTime = time();
1081: $iStartTime = $iStopTime - 1;
1082: }
1083: $sCpanelResponse = $this->executeCpanelAction(
1084: $oCpanel,
1085: 'Email',
1086: 'add_auto_responder',
1087: [
1088: 'email' => $sEmail,
1089: 'from' => '',
1090: 'subject' => $sSubject,
1091: 'body' => $sMessage,
1092: 'domain' => $sDomain,
1093: 'is_html' => 0,
1094: 'interval' => 8,
1095: 'start' => $iStartTime,
1096: 'stop' => $iStopTime
1097: ]
1098: );
1099: $aResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1100: }
1101:
1102: return $aResult;
1103: }
1104:
1105: /**
1106: * Deletes autoresponder from cPanel.
1107: * @param string $sEmail
1108: */
1109: protected function deleteAutoresponder($sEmail, $iTenantId = 0)
1110: {
1111: $oCpanel = $this->getCpanel($iTenantId);
1112: if ($oCpanel && $sEmail) {
1113: $sCpanelResponse = $this->executeCpanelAction(
1114: $oCpanel,
1115: 'Email',
1116: 'delete_auto_responder',
1117: [
1118: 'email' => $sEmail,
1119: ]
1120: );
1121: self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1122: }
1123: }
1124:
1125: /**
1126: * Obtains filters from cPanel.
1127: * @param object $oAccount
1128: * @return array
1129: */
1130: protected function getFilters($oAccount, $iTenantId = 0)
1131: {
1132: $aFilters = [];
1133:
1134: $oCpanel = $this->getCpanel($iTenantId);
1135: if ($oCpanel && $oAccount) {
1136: $sCpanelResponse = $this->executeCpanelAction(
1137: $oCpanel,
1138: 'Email',
1139: 'list_filters',
1140: [
1141: 'account' => $oAccount->Email
1142: ]
1143: );
1144: $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1145: if ($aParseResult && isset($aParseResult['Data'])) {
1146: $aFilters = self::convertCPanelFIltersToWebmailFIlters($aParseResult['Data'], $oAccount);
1147: }
1148: }
1149:
1150: return $aFilters;
1151: }
1152:
1153: /**
1154: * Removes filters supported by the system from cPanel.
1155: * @param object $oAccount
1156: * @return boolean
1157: */
1158: protected function removeSupportedFilters($oAccount, $iTenantId = 0)
1159: {
1160: $bResult = false;
1161:
1162: if ($oAccount) {
1163: $aFilters = $this->getFilters($oAccount, $iTenantId);
1164: if (is_array($aFilters)) {
1165: if (count($aFilters) === 0) {
1166: $bResult = true;
1167: } else {
1168: $aSuportedFilterNames = array_map(function ($aFilter) {
1169: return $aFilter['Filtername'];
1170: }, $aFilters);
1171:
1172: $oCpanel = $this->getCpanel($iTenantId);
1173: if ($oCpanel) {
1174: $bDelResult = true;
1175: foreach ($aSuportedFilterNames as $sSuportedFilterName) {
1176: $sCpanelResponse = $this->executeCpanelAction(
1177: $oCpanel,
1178: 'Email',
1179: 'delete_filter',
1180: [
1181: 'account' => $oAccount->Email,
1182: 'filtername' => $sSuportedFilterName
1183: ]
1184: );
1185: $aDelResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1186: if (!$aDelResult['Status']) {
1187: $bDelResult = false;
1188: break;
1189: }
1190: }
1191: $bResult = $bDelResult;
1192: }
1193: }
1194: }
1195: }
1196:
1197: return $bResult;
1198: }
1199:
1200: /**
1201: * Obtains namespace for mail account from IMAP.
1202: * @staticvar object $oNamespace
1203: * @param object $oAccount
1204: * @return object
1205: */
1206: protected static function getImapNamespace($oAccount)
1207: {
1208: static $oNamespace = null;
1209:
1210: if ($oNamespace === null) {
1211: $oNamespace = MailModule::getInstance()->getMailManager()->_getImapClient($oAccount)->GetNamespace();
1212: }
1213:
1214: return $oNamespace;
1215: }
1216:
1217: /**
1218: * Parses response from cPanel.
1219: * @param string $sResponse
1220: * @param boolean $bAllowException
1221: * @return array
1222: * @throws \Exception
1223: */
1224: protected static function parseResponse($sResponse, $bAllowException = true)
1225: {
1226: $aResult = [
1227: 'Status' => false
1228: ];
1229:
1230: $oResult = \json_decode($sResponse);
1231:
1232: if ($oResult
1233: && isset($oResult->result)
1234: && isset($oResult->result->status)
1235: && $oResult->result->status === 1
1236: && (isset($oResult->result->data)
1237: || empty($oResult->result->data))
1238: ) {
1239: $aResult = [
1240: 'Status' => true,
1241: 'Data' => $oResult->result->data ?? null
1242: ];
1243: } elseif ($oResult && isset($oResult->error)) {
1244: $aResult = [
1245: 'Status' => false,
1246: 'Error' => $oResult->error
1247: ];
1248: } elseif ($oResult && isset($oResult->result)
1249: && isset($oResult->result->errors) && !empty($oResult->result->errors)
1250: && isset($oResult->result->errors[0])) {
1251: $aResult = [
1252: 'Status' => false,
1253: 'Error' => $oResult->result->errors[0]
1254: ];
1255: } else {
1256: $aResult = [
1257: 'Status' => false,
1258: 'Error' => $sResponse
1259: ];
1260: }
1261:
1262: if ($bAllowException && $aResult['Status'] === false) {
1263: throw new \Exception(trim($aResult['Error'], ' ()'));
1264: }
1265:
1266: return $aResult;
1267: }
1268:
1269: /**
1270: * Converts cPanel filters to webmail filters.
1271: * @param array $aCPanelFilters
1272: * @param object $oAccount
1273: * @return array
1274: */
1275: protected static function convertCPanelFIltersToWebmailFIlters($aCPanelFilters, $oAccount)
1276: {
1277: $aResult = [];
1278:
1279: foreach ($aCPanelFilters as $oCPanelFilter) {
1280: $iAction = null;
1281: $iCondition = null;
1282: $iField = null;
1283:
1284: if ($oCPanelFilter->actions[0]->action === 'save') {
1285: if ($oCPanelFilter->actions[0]->dest === '/dev/null') {
1286: $iAction = \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately;
1287: } else {
1288: $iAction = \Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder;
1289: }
1290: }
1291:
1292: $sDestEmail = "";
1293: if ($oCPanelFilter->actions[0]->action === 'deliver') {
1294: $sDestEmail = $oCPanelFilter->actions[0]->dest;
1295: $iAction = \Aurora\Modules\Mail\Enums\FilterAction::Redirect;
1296: }
1297:
1298: switch ($oCPanelFilter->rules[0]->match) {
1299: case 'contains':
1300: $iCondition = \Aurora\Modules\Mail\Enums\FilterCondition::ContainSubstring;
1301: break;
1302: case 'does not contain':
1303: $iCondition = \Aurora\Modules\Mail\Enums\FilterCondition::NotContainSubstring;
1304: break;
1305: case 'is':
1306: $iCondition = \Aurora\Modules\Mail\Enums\FilterCondition::ContainExactPhrase;
1307: break;
1308: }
1309:
1310: switch ($oCPanelFilter->rules[0]->part) {
1311: case '$header_from:':
1312: $iField = \Aurora\Modules\Mail\Enums\FilterFields::From;
1313: break;
1314: case '$header_to:':
1315: $iField = \Aurora\Modules\Mail\Enums\FilterFields::To;
1316: break;
1317: case '$header_subject:':
1318: $iField = \Aurora\Modules\Mail\Enums\FilterFields::Subject;
1319: break;
1320: }
1321: $oNamespace = self::getImapNamespace($oAccount);
1322:
1323: $sNamespace = \str_replace($oNamespace->GetPersonalNamespaceDelimiter(), '', $oNamespace->GetPersonalNamespace());
1324:
1325: $aFolderNameParts = \explode('/', $oCPanelFilter->actions[0]->dest);
1326: $sFolderFullName = '';
1327: if (\count($aFolderNameParts) > 1 && $aFolderNameParts[\count($aFolderNameParts) - 1] !== $sNamespace) {
1328: $sFolderFullName = $sNamespace . $aFolderNameParts[\count($aFolderNameParts) - 1];
1329: }
1330:
1331: if (isset($iAction) && isset($iCondition) && isset($iField)
1332: && (!empty($sFolderFullName) || $iAction === \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately || $iAction === \Aurora\Modules\Mail\Enums\FilterAction::Redirect)
1333: ) {
1334: $aResult[] = [
1335: 'Action' => $iAction,
1336: 'Condition' => $iCondition,
1337: 'Enable' => (bool) $oCPanelFilter->enabled,
1338: 'Field' => $iField,
1339: 'Filter' => $oCPanelFilter->rules[0]->val,
1340: 'FolderFullName' => $iAction === \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately ? '' : $sFolderFullName,
1341: 'Filtername' => $oCPanelFilter->filtername,
1342: 'Email' => $sDestEmail
1343: ];
1344: }
1345: }
1346:
1347: return $aResult;
1348: }
1349:
1350: /**
1351: * Converts webmail filters to cPanel filters.
1352: * @param array $aWebmailFilter
1353: * @param object $oAccount
1354: * @return array
1355: */
1356: protected static function convertWebmailFIlterToCPanelFIlter($aWebmailFilter, $oAccount)
1357: {
1358: $sAction = '';
1359: $sPart = '';
1360: $sMatch = '';
1361: $oNamespace = self::getImapNamespace($oAccount);
1362: $sNamespace = \str_replace($oNamespace->GetPersonalNamespaceDelimiter(), '', $oNamespace->GetPersonalNamespace());
1363: $sDest = \str_replace($sNamespace, '/', $aWebmailFilter["FolderFullName"]);
1364:
1365: switch ($aWebmailFilter["Action"]) {
1366: case \Aurora\Modules\Mail\Enums\FilterAction::DeleteFromServerImmediately:
1367: $sDest = '/dev/null';
1368: // no break
1369: case \Aurora\Modules\Mail\Enums\FilterAction::MoveToFolder:
1370: $sAction = 'save';
1371: break;
1372: case \Aurora\Modules\Mail\Enums\FilterAction::Redirect:
1373: $sAction = 'deliver';
1374: $sDest = isset($aWebmailFilter['Email']) ? $aWebmailFilter['Email'] : $sDest;
1375: break;
1376: }
1377:
1378: switch ($aWebmailFilter["Condition"]) {
1379: case \Aurora\Modules\Mail\Enums\FilterCondition::ContainSubstring:
1380: $sMatch = 'contains';
1381: break;
1382: case \Aurora\Modules\Mail\Enums\FilterCondition::NotContainSubstring:
1383: $sMatch = 'does not contain';
1384: break;
1385: case \Aurora\Modules\Mail\Enums\FilterCondition::ContainExactPhrase:
1386: $sMatch = 'is';
1387: break;
1388: }
1389:
1390: switch ($aWebmailFilter["Field"]) {
1391: case \Aurora\Modules\Mail\Enums\FilterFields::From:
1392: $sPart = '$header_from:';
1393: break;
1394: case \Aurora\Modules\Mail\Enums\FilterFields::To:
1395: $sPart = '$header_to:';
1396: break;
1397: case \Aurora\Modules\Mail\Enums\FilterFields::Subject:
1398: $sPart = '$header_subject:';
1399: break;
1400: }
1401:
1402: return [
1403: 'filtername' => \uniqid(),
1404: 'account' => $oAccount->Email,
1405: 'action1' => $sAction,
1406: 'dest1' => $sDest,
1407: 'part1' => $sPart,
1408: 'match1' => $sMatch,
1409: 'val1' => $aWebmailFilter['Filter'],
1410: 'opt1' => 'or',
1411: ];
1412: }
1413:
1414: /**
1415: * Obtains list of module settings for authenticated user.
1416: *
1417: * @param int|null $TenantId Tenant ID
1418: *
1419: * @return array
1420: */
1421: public function GetSettings($TenantId = null)
1422: {
1423: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1424: $oSettings = $this->oModuleSettings;
1425: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1426: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User) {
1427: if (empty($TenantId)) {
1428: if ($oAuthenticatedUser->isNormalOrTenant()) {
1429: $aResult = ['AllowAliases' => $oSettings->AllowAliases];
1430: if ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin) {
1431: $aResult['AllowCreateDeleteAccountOnCpanel'] = $oSettings->AllowCreateDeleteAccountOnCpanel;
1432: }
1433: return $aResult;
1434: } elseif ($oAuthenticatedUser->isAdmin()) {
1435: return [
1436: 'CpanelHost' => $oSettings->CpanelHost,
1437: 'CpanelPort' => $oSettings->CpanelPort,
1438: 'CpanelUser' => $oSettings->CpanelUser,
1439: 'CpanelHasPassword' => $oSettings->CpanelPassword !== '',
1440: 'AllowAliases' => $oSettings->AllowAliases,
1441: 'AllowCreateDeleteAccountOnCpanel' => $oSettings->AllowCreateDeleteAccountOnCpanel,
1442: ];
1443: }
1444: } else {
1445: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
1446: $oTenant = \Aurora\System\Api::getTenantById($TenantId);
1447:
1448: if ($oTenant && ($oAuthenticatedUser->isAdmin() || ($oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oAuthenticatedUser->IdTenant === $oTenant->Id))) {
1449: return [
1450: 'CpanelHost' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelHost', ''),
1451: 'CpanelPort' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelPort', ''),
1452: 'CpanelUser' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelUser', ''),
1453: 'CpanelHasPassword' => $oSettings->GetTenantValue($oTenant->Name, 'CpanelPassword', '') !== '',
1454: ];
1455: }
1456: }
1457: }
1458:
1459: return [];
1460: }
1461:
1462: /**
1463: * Updates module's settings - saves them to config.json file or to user settings in db.
1464: * @param string $CpanelHost
1465: * @param string $CpanelPort
1466: * @param string $CpanelUser
1467: * @param int $CpanelPassword
1468: * @param int|null $TenantId
1469: *
1470: * @return boolean
1471: */
1472: public function UpdateSettings($CpanelHost, $CpanelPort, $CpanelUser, $CpanelPassword, $TenantId = null)
1473: {
1474: $result = false;
1475:
1476: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
1477: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1478:
1479: $oSettings = $this->oModuleSettings;
1480: if (!empty($TenantId)) {
1481:
1482: $oTenant = \Aurora\System\Api::getTenantById($TenantId);
1483:
1484: if ($oTenant && ($oAuthenticatedUser->isAdmin() || $oAuthenticatedUser->IdTenant === $oTenant->Id)) {
1485: $aValues = [
1486: 'CpanelHost' => $CpanelHost,
1487: 'CpanelPort' => $CpanelPort,
1488: 'CpanelUser' => $CpanelUser
1489: ];
1490: if ($CpanelPassword !== '') {
1491: $aValues['CpanelPassword'] = $CpanelPassword;
1492: } else {
1493: $aValues['CpanelPassword'] = $oSettings->GetTenantValue($oTenant->Name, 'CpanelPassword');
1494: }
1495: $result = $oSettings->SaveTenantSettings($oTenant->Name, $aValues);
1496: }
1497: } else {
1498: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
1499:
1500: $oSettings->CpanelHost = $CpanelHost;
1501: $oSettings->CpanelPort = $CpanelPort;
1502: $oSettings->CpanelUser = $CpanelUser;
1503: if ($CpanelPassword !== '') {
1504: $oSettings->CpanelPassword = $CpanelPassword;
1505: }
1506: $result = $oSettings->Save();
1507: }
1508:
1509: return $result;
1510: }
1511:
1512: /**
1513: * Obtains all aliases for specified user.
1514: * @param int $UserId User identifier.
1515: * @return array|boolean
1516: */
1517: public function GetAliases($UserId)
1518: {
1519: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1520:
1521: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($UserId);
1522: $bUserFound = $oUser instanceof \Aurora\Modules\Core\Models\User;
1523: if ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::NormalUser && $oUser->Id === $oAuthenticatedUser->Id) {
1524: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1525: } elseif ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
1526: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
1527: } else {
1528: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
1529: }
1530: $oAccount = MailModule::Decorator()->GetAccountByEmail($oUser->PublicId, $oUser->Id);
1531: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
1532: $aForwardersFromEmail = [];
1533: $sEmail = $oAccount->Email;
1534: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
1535:
1536: $aAliases = $this->getManager('Aliases')->getAliasesByUserId($oUser->Id);
1537: //is Server Supported
1538: $oServer = MailModule::getInstance()->getServersManager()->getServer($oAccount->ServerId);
1539: $bSupported = in_array($oServer->IncomingServer, $this->oModuleSettings->SupportedServers);
1540: if ($bSupported) {
1541: $aServerDomainsByTenant = MailModule::Decorator()->GetServerDomains($oAccount->ServerId, $oUser->IdTenant);
1542: //Get forwarders for all supported domains
1543: $aForwarders = [];
1544: foreach ($aServerDomainsByTenant as $sServerDomain) {
1545: $aForwarders = array_merge($aForwarders, $this->getDomainForwarders($sEmail, $sServerDomain, $oUser->IdTenant));
1546: }
1547:
1548: //filter forvarders
1549: $aFilteredForwarders = $this->getAliasesFromForwarders($aForwarders, $oUser->IdTenant);
1550: foreach ($aFilteredForwarders as $oForwarder) {
1551: $sFromEmail = $oForwarder->dest;
1552: $sToEmail = $oForwarder->forward;
1553: if ($sToEmail === $sEmail) {
1554: $aForwardersFromEmail[] = $sFromEmail;
1555: }
1556: }
1557: $aAliasesEmail = $aAliases->map(function ($oAlias) {
1558: return $oAlias->Email;
1559: })->toArray();
1560:
1561: foreach ($aForwardersFromEmail as $sForwarderFromEmail) {
1562: if (!in_array($sForwarderFromEmail, $aAliasesEmail)) {
1563: //Check if an alias exists
1564: $aAliasesCheck = $this->getManager('Aliases')->getAliases(0, 0, Models\Alias::where('Email', $sForwarderFromEmail));
1565: if (empty($aAliasesCheck)) {
1566: //create alias if doesn't exists
1567: $oAlias = new \Aurora\Modules\CpanelIntegrator\Models\Alias();
1568: $oAlias->IdUser = $oUser->Id;
1569: $oAlias->IdAccount = $oAccount->Id;
1570: $oAlias->Email = $sForwarderFromEmail;
1571: $oAlias->ForwardTo = $oAccount->Email;
1572: $this->getManager('Aliases')->createAlias($oAlias);
1573: }
1574: }
1575: }
1576: foreach ($aAliases as $oAlias) {
1577: if (!in_array($oAlias->Email, $aForwardersFromEmail)) {
1578: $this->getManager('Aliases')->deleteAlias($oAlias);
1579: }
1580: }
1581: $aAliases = $this->getManager('Aliases')->getAliasesByUserId($oUser->Id);
1582: }
1583:
1584: return [
1585: 'Domain' => $sDomain,
1586: 'Aliases' => $aForwardersFromEmail,
1587: 'ObjAliases' => $aAliases->all()
1588: ];
1589: }
1590:
1591: return false;
1592: }
1593:
1594: /**
1595: * Creates new alias with specified name and domain.
1596: * @param int $UserId User identifier.
1597: * @param string $AliasName Alias name.
1598: * @param string $AliasDomain Alias domain.
1599: * @return boolean|int
1600: */
1601: public function AddNewAlias($UserId, $AliasName, $AliasDomain)
1602: {
1603: $AliasName = strtolower($AliasName); // cPanel creates an alias with a lowercase name.
1604: $mResult = false;
1605: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1606:
1607: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($UserId);
1608: $bUserFound = $oUser instanceof \Aurora\Modules\Core\Models\User;
1609: if ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::NormalUser && $UserId === $oAuthenticatedUser->Id) {
1610: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1611: } elseif ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
1612: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
1613: } else {
1614: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
1615: }
1616:
1617: $oMailDecorator = MailModule::Decorator();
1618: $oAccount = $bUserFound && $oMailDecorator ? $oMailDecorator->GetAccountByEmail($oUser->PublicId, $oUser->Id) : null;
1619: $aServerDomainsByTenant = $oMailDecorator ? $oMailDecorator->GetServerDomains($oAccount->ServerId, $oUser->IdTenant) : [];
1620: //AliasDomain must be in the tenant’s domain list
1621: if (!in_array($AliasDomain, $aServerDomainsByTenant)) {
1622: throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::DomainOutsideTenant);
1623: }
1624: //Checking if an alias matches an existing account
1625: $oCpanel = $this->getCpanel($oUser->IdTenant);
1626: if ($oCpanel) {
1627: $sCpanelResponse = $this->executeCpanelAction($oCpanel, 'Email', 'list_pops', []);
1628: $aParseResult = self::parseResponse($sCpanelResponse);
1629: if ($aParseResult && isset($aParseResult['Data'])) {
1630: $aEmailAccountsOnCPanel = array_map(function ($oAccount) {
1631: return $oAccount->email;
1632: }, $aParseResult['Data']);
1633: if (in_array($AliasName . '@' . $AliasDomain, $aEmailAccountsOnCPanel)) {
1634: throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::AliaMatchesExistingEmail);
1635: }
1636: }
1637: }
1638: //Check if an alias exists on CPanel
1639: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
1640: $aResult = $this->getForwarder($AliasDomain, $AliasName . '@' . $AliasDomain, $oUser->IdTenant);
1641: $bAliasExists = is_array($aResult) && isset($aResult['Email']) && !empty($aResult['Email']);
1642: if ($bAliasExists) {
1643: throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::AliasAlreadyExists);
1644: }
1645: //Check if an alias exists
1646: $aAliases = $this->getManager('Aliases')->getAliases(0, 0, Models\Alias::where('Email', $AliasName . '@' . $AliasDomain));
1647: if ($aAliases->count() > 0) {
1648: throw new \Aurora\System\Exceptions\ApiException(Enums\ErrorCodes::AliasAlreadyExists);
1649: }
1650:
1651: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
1652: && $this->isAccountServerSupported($oAccount)) {
1653: $sEmail = $oAccount->Email;
1654: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email);
1655: $aServerDomainsByTenant = $oMailDecorator ? $oMailDecorator->GetServerDomains($oAccount->ServerId, $oUser->IdTenant) : [];
1656: //Get forwarders for all supported domains
1657: $aForwarders = [];
1658: foreach ($aServerDomainsByTenant as $sServerDomain) {
1659: $aForwarders = array_merge($aForwarders, $this->getDomainForwarders($sEmail, $sServerDomain, $oUser->IdTenant));
1660: }
1661: $aDomainAliases = $this->getAliasesFromForwarders($aForwarders, $oUser->IdTenant);
1662: $aUserAliases = [];
1663: foreach ($aDomainAliases as $oAlias) {
1664: $sToEmail = $oAlias->forward;
1665: if ($sToEmail === $sEmail) {
1666: $aUserAliases[] = $oAlias;
1667: }
1668: }
1669: $aArgs = [
1670: 'TenantId' => $oUser->IdTenant,
1671: 'DomainAliases' => $aDomainAliases,
1672: 'UserAliases' => $aUserAliases,
1673: 'AliasName' => $AliasName,
1674: 'AliasDomain' => $AliasDomain,
1675: 'ToEmail' => $oAccount->Email,
1676: 'UserId' => $oUser->Id
1677: ];
1678: $this->broadcastEvent(
1679: 'CreateAlias::before',
1680: $aArgs
1681: );
1682: $bCreateForwardewResult = $this->createForwarder($AliasDomain, $AliasName . '@' . $AliasDomain, $oAccount->Email, $oUser->IdTenant);
1683: if ($bCreateForwardewResult) {
1684: //create Alias
1685: $oAlias = new \Aurora\Modules\CpanelIntegrator\Models\Alias();
1686: $oAlias->IdUser = $oUser->Id;
1687: $oAlias->IdAccount = $oAccount->Id;
1688: $oAlias->Email = $AliasName . '@' . $AliasDomain;
1689: $oAlias->ForwardTo = $oAccount->Email;
1690: $mResult = $this->getManager('Aliases')->createAlias($oAlias);
1691: }
1692: }
1693:
1694: return $mResult;
1695: }
1696:
1697: /**
1698: * Update existing Alias
1699: *
1700: * @param int $UserId
1701: * @param int $AccountID
1702: * @param string $FriendlyName
1703: * @param int $EntityId
1704: * @return bool
1705: * @throws \Aurora\System\Exceptions\ApiException
1706: */
1707: public function UpdateAlias($UserId, $AccountID, $FriendlyName, $EntityId)
1708: {
1709: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1710:
1711: $bResult = false;
1712: $oUser = \Aurora\System\Api::getAuthenticatedUser();
1713: $oAccount = MailModule::Decorator()->GetAccount($AccountID);
1714: if (!$oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount || $oAccount->IdUser !== $oUser->Id) {
1715: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
1716: }
1717: $oAlias = $this->getManager('Aliases')->getAlias((int) $EntityId);
1718: if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias
1719: && $oAlias->IdUser === $oUser->Id
1720: ) {
1721: $oAlias->FriendlyName = $FriendlyName;
1722: $bResult = $this->getManager('Aliases')->updateAlias($oAlias);
1723: }
1724:
1725: return $bResult;
1726: }
1727:
1728: /**
1729: * Deletes aliases with specified emails.
1730: * @param int $UserId User identifier
1731: * @param array $Aliases Aliases emails.
1732: * @return boolean
1733: */
1734: public function DeleteAliases($UserId, $Aliases)
1735: {
1736: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1737:
1738: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($UserId);
1739: $bUserFound = $oUser instanceof \Aurora\Modules\Core\Models\User;
1740:
1741: if ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::NormalUser && $oUser->Id === $oAuthenticatedUser->Id) {
1742: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1743: } elseif ($bUserFound && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
1744: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
1745: } else {
1746: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::SuperAdmin);
1747: }
1748:
1749: $bResult = false;
1750: $oAccount = $bUserFound ? MailModule::Decorator()->GetAccountByEmail($oUser->PublicId, $oUser->Id) : null;
1751: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount
1752: && $this->isAccountServerSupported($oAccount)) {
1753: foreach ($Aliases as $sAlias) {
1754: preg_match('/(.+)@(.+)$/', $sAlias, $aMatches);
1755: $AliasName = isset($aMatches[1]) ? $aMatches[1] : '';
1756: $AliasDomain = isset($aMatches[2]) ? $aMatches[2] : '';
1757: $bResult = $this->deleteForwarder($AliasName . '@' . $AliasDomain, $oAccount->Email, $oUser->IdTenant);
1758: if ($bResult) {
1759: $oAlias = $this->getManager('Aliases')->getUserAliasByEmail($oUser->Id, $AliasName . '@' . $AliasDomain);
1760: if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias) {
1761: $this->getManager('Aliases')->deleteAlias($oAlias);
1762: }
1763: }
1764: }
1765: }
1766:
1767: return $bResult;
1768: }
1769:
1770: public function UpdateSignature($UserId, $AliasId = null, $UseSignature = null, $Signature = null)
1771: {
1772: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1773:
1774: $bResult = false;
1775: $oUser = \Aurora\System\Api::getAuthenticatedUser();
1776: if ($oUser->Id === $UserId) {
1777: $oAlias = $this->getManager('Aliases')->getAlias((int) $AliasId);
1778: if ($oAlias instanceof \Aurora\Modules\CpanelIntegrator\Models\Alias && $oAlias->IdUser === $oUser->Id) {
1779: $oAlias->UseSignature = $UseSignature;
1780: $oAlias->Signature = $Signature;
1781: $bResult = $this->getManager('Aliases')->updateAlias($oAlias);
1782: }
1783: }
1784:
1785: return $bResult;
1786: }
1787:
1788: protected function getAliasesFromForwarders($aForwarders, $iTenantId = 0)
1789: {
1790: $aResult = [];
1791: $oCpanel = $this->getCpanel($iTenantId);
1792: if ($oCpanel) {
1793: $sCpanelResponse = $this->executeCpanelAction($oCpanel, 'Email', 'list_pops', []);
1794: $aParseResult = self::parseResponse($sCpanelResponse);
1795: if ($aParseResult && isset($aParseResult['Data'])) {
1796: $aEmailAccountsOnCPanel = array_map(function ($oAccount) {
1797: return $oAccount->email;
1798: }, $aParseResult['Data']);
1799: foreach ($aForwarders as &$oForwarder) {
1800: //if 'dest' is the email address of the real account, it is not an alias
1801: if (!in_array($oForwarder->dest, $aEmailAccountsOnCPanel)) {
1802: $aResult[] = $oForwarder;
1803: }
1804: }
1805: }
1806: }
1807:
1808: return $aResult;
1809: }
1810:
1811: protected function isAliasExists($sEmail, $iTenantId = 0)
1812: {
1813: //Check if an alias exists on CPanel
1814: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
1815: $aResult = $this->getForwarder($sDomain, $sEmail, $iTenantId);
1816: $bAliasExists = is_array($aResult) && isset($aResult['Email']) && !empty($aResult['Email']);
1817:
1818: return $bAliasExists;
1819: }
1820:
1821: public function GetScriptForward($AccountID)
1822: {
1823: $aResult = [];
1824:
1825: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1826: $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($AccountID);
1827: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
1828: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
1829: // check if account belongs to authenticated user
1830: && ($oAccount->IdUser === $oAuthenticatedUser->Id
1831: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
1832: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
1833: $oCpanel = null;
1834: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($oAccount->IdUser);
1835: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
1836: $oCpanel = $this->getCpanel($oUser->IdTenant);
1837: $sEmail = $oAccount->Email;
1838: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oUser->PublicId);
1839: $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
1840: if ($oCpanel && $sDomain && $sEmail) {
1841: $sCpanelResponse = $this->executeCpanelAction(
1842: $oCpanel,
1843: 'Email',
1844: 'list_forwarders',
1845: [
1846: 'domain' => $sDomain,
1847: 'regex' => '^' . $sEmail . '$'
1848: ]
1849: );
1850: $aParseResult = self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1851: if ($aParseResult
1852: && isset($aParseResult['Data'])
1853: && is_array($aParseResult['Data'])
1854: && count($aParseResult['Data']) > 0
1855: ) {
1856: foreach ($aParseResult['Data'] as $oData) {
1857: if ($oData->forward === '|' . $sForwardScriptPath) {
1858: $aResult = [
1859: 'Email' => $oData->forward
1860: ];
1861: break;
1862: }
1863: }
1864: }
1865: } else {
1866: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::Validation_InvalidParameters);
1867: }
1868: }
1869: }
1870: }
1871:
1872: return $aResult;
1873: }
1874:
1875: public function CreateScriptForward($AccountID)
1876: {
1877: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1878: $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($AccountID);
1879: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
1880: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
1881: // check if account belongs to authenticated user
1882: && ($oAccount->IdUser === $oAuthenticatedUser->Id
1883: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
1884: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
1885: $oCpanel = null;
1886: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($oAccount->IdUser);
1887: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
1888: $oCpanel = $this->getCpanel($oUser->IdTenant);
1889: $sEmail = $oAccount->Email;
1890: $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oUser->PublicId);
1891: $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
1892:
1893: if ($oCpanel && $sDomain && $sEmail) {
1894: $sCpanelResponse = $this->executeCpanelAction(
1895: $oCpanel,
1896: 'Email',
1897: 'add_forwarder',
1898: [
1899: 'domain' => $sDomain,
1900: 'email' => $sEmail,
1901: 'fwdopt' => 'pipe',
1902: 'pipefwd' => $sForwardScriptPath
1903: ]
1904: );
1905: self::parseResponse($sCpanelResponse); // throws exception in case if error has occured
1906: return true;
1907: } else {
1908: throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Exceptions\Errs::Validation_InvalidParameters);
1909: }
1910: }
1911: }
1912: }
1913:
1914: return false;
1915: }
1916:
1917: public function RemoveScriptForward($AccountID)
1918: {
1919: $bResult = false;
1920: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
1921: $oAccount = \Aurora\Modules\Mail\Module::getInstance()->GetAccount($AccountID);
1922: if ($oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount && $this->isAccountServerSupported($oAccount)) {
1923: if ($oAuthenticatedUser instanceof \Aurora\Modules\Core\Models\User
1924: // check if account belongs to authenticated user
1925: && ($oAccount->IdUser === $oAuthenticatedUser->Id
1926: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin
1927: || $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin)) {
1928: $sFromEmail = $oAccount->Email;
1929: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($oAccount->IdUser);
1930: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
1931: $sForwardScriptPath = $this->oModuleSettings->ForwardScriptPath ?: \dirname(__FILE__) . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'process_mail.php';
1932: try {
1933: $this->deleteForwarder($sFromEmail, '|' . $sForwardScriptPath, $oUser->IdTenant);
1934: $bResult = true;
1935: } catch (\Exception $oEx) {
1936: $bResult = false;
1937: }
1938: }
1939: }
1940: }
1941:
1942: return $bResult;
1943: }
1944: }
1945: