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\InvitationLinkWebclient;
9:
10: use Aurora\System\Api;
11: use Aurora\System\Application;
12: use PHPMailer\PHPMailer\PHPMailer;
13:
14: /**
15: * Creates invitation link upon creating user in admin panel, and allows registering new user account with this link.
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\AbstractWebclientModule
26: {
27: protected $oMinModuleDecorator;
28:
29: protected $aRequireModules = array(
30: 'Min'
31: );
32:
33: /**
34: * @return Module
35: */
36: public static function getInstance()
37: {
38: return parent::getInstance();
39: }
40:
41: /**
42: * @return Module
43: */
44: public static function Decorator()
45: {
46: return parent::Decorator();
47: }
48:
49: /**
50: * @return Settings
51: */
52: public function getModuleSettings()
53: {
54: return $this->oModuleSettings;
55: }
56:
57: /***** private functions *****/
58: /**
59: * Initializes module.
60: *
61: * @ignore
62: */
63: public function init()
64: {
65: $this->subscribeEvent('Register::before', array($this, 'onBeforeRegister'));
66: $this->subscribeEvent('Register::after', array($this, 'onAfterRegister'));
67:
68: $this->subscribeEvent('Core::CreateUser::after', array($this, 'onAfterCreateUser'));
69:
70: $this->subscribeEvent('StandardAuth::CreateAuthenticatedUserAccount::after', array($this, 'onAfterCreateUserAccount'));
71:
72: $this->subscribeEvent('CreateOAuthAccount', array($this, 'onCreateOAuthAccount'));
73: $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser'));
74: }
75:
76: /**
77: * Returns Min module decorator.
78: *
79: * @return \Aurora\Modules\Min\Module
80: */
81: private function getMinModuleDecorator()
82: {
83: if ($this->oMinModuleDecorator === null) {
84: $this->oMinModuleDecorator = \Aurora\Modules\Min\Module::Decorator();
85: }
86:
87: return $this->oMinModuleDecorator;
88: }
89:
90: /**
91: * Returns register module hash.
92: *
93: * @return string
94: */
95: protected function getRegisterModuleHash()
96: {
97: $sResult = null;
98: $oRegisterModuleDecorator = \Aurora\System\Api::GetModuleDecorator($this->oModuleSettings->RegisterModuleName);
99: if ($oRegisterModuleDecorator && method_exists($oRegisterModuleDecorator, 'GetSettings')) {
100: $oRegisterModuleSettings = $oRegisterModuleDecorator->GetSettings();
101: $sResult = $oRegisterModuleSettings['HashModuleName'];
102: }
103:
104: return $sResult;
105: }
106:
107: /**
108: * Returns login module hash.
109: *
110: * @return string
111: */
112: protected function getLoginModuleHash()
113: {
114: $sResult = null;
115: $oLoginModuleDecorator = \Aurora\System\Api::GetModuleDecorator($this->oModuleSettings->LoginModuleName);
116: if ($oLoginModuleDecorator && method_exists($oLoginModuleDecorator, 'GetSettings')) {
117: $oLoginModuleSettings = $oLoginModuleDecorator->GetSettings();
118: $sResult = $oLoginModuleSettings['HashModuleName'];
119: }
120:
121: return $sResult;
122: }
123:
124: /**
125: * Returns id for Min Module
126: *
127: * @return string
128: */
129: protected function generateMinId($iUserId)
130: {
131: return \implode('|', array(self::GetName(), $iUserId, \md5($iUserId)));
132: }
133:
134: /**
135: * Returns user with identifier obtained from the Invitation link hash.
136: *
137: * @param string $InvitationLinkHash Invitation link hash.
138: * @return \Aurora\Modules\Core\Models\User
139: */
140: protected function getUserByInvitationLinkHash($InvitationLinkHash)
141: {
142: $oUser = null;
143: $oMin = $this->getMinModuleDecorator();
144: if ($oMin) {
145: $mHash = $oMin->GetMinByHash($InvitationLinkHash);
146: if (isset($mHash['__hash__'], $mHash['UserId']) && !isset($mHash['Registered'])) {
147: $iUserId = $mHash['UserId'];
148: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($iUserId);
149: }
150: }
151: return $oUser;
152: }
153:
154: /**
155: * Writes to $aArgs['UserId'] user identifier obtained from Invitation link hash.
156: *
157: * @ignore
158: * @param array $aArgs
159: * @param mixed $mResult
160: */
161: public function onBeforeRegister(&$aArgs, &$mResult)
162: {
163: if (empty($aArgs['InvitationLinkHash'])) {
164: return true; // break other subscriptions and Register method itself to prevent creation of a new user
165: } else {
166: $oUser = $this->getUserByInvitationLinkHash($aArgs['InvitationLinkHash']);
167: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
168: $aArgs['UserId'] = $oUser->Id;
169: } else {
170: return true; // break other subscriptions and Register method itself to prevent creation of a new user
171: }
172: }
173: }
174:
175: /**
176: * Updates Invitation link hash in Min module.
177: *
178: * @ignore
179: * @param array $aArgs
180: * @param mixed $mResult
181: */
182: public function onAfterRegister($aArgs, &$mResult)
183: {
184: if ($mResult && !empty($aArgs['InvitationLinkHash'])) {
185: $oMin = $this->getMinModuleDecorator();
186: if ($oMin) {
187: $mHash = $oMin->GetMinByHash($aArgs['InvitationLinkHash']);
188: if (isset($mHash['__hash__'], $mHash['UserId']) && !isset($mHash['Registered'])) {
189: $oMin->DeleteMinByHash($mHash['__hash__']);
190: }
191: }
192: }
193: }
194:
195: /**
196: * Updates Invitation link hash in Min module for user with $aArgs['UserId'] identifier.
197: *
198: * @ignore
199: * @param array $aArgs
200: * @param mixed $mResult
201: */
202: public function onAfterCreateUserAccount($aArgs, &$mResult)
203: {
204: $userId = Api::getUserIdByPublicId($aArgs['Login']);
205: $oMin = $this->getMinModuleDecorator();
206: if ($oMin) {
207: $mHash = $oMin->GetMinById(
208: $this->generateMinId($userId)
209: );
210:
211: if ($userId && isset($mHash['__hash__']) && !isset($mHash['Registered'])) {
212: $oMin->DeleteMinByHash($mHash['__hash__']);
213: }
214: }
215: }
216:
217: /**
218: * Writes to $oUser variable user object for Invitation link hash from cookie.
219: *
220: * @ignore
221: *
222: * @param array $aArgs
223: * @param \Aurora\Modules\Core\Models\User $oUser
224: */
225: public function onCreateOAuthAccount($aArgs, &$oUser)
226: {
227: if (isset($_COOKIE['InvitationLinkHash'])) {
228: $InvitationLinkHash = $_COOKIE['InvitationLinkHash'];
229:
230: $oFoundUser = $this->getUserByInvitationLinkHash($InvitationLinkHash);
231: if ($oFoundUser) {
232: unset($_COOKIE['InvitationLinkHash']);
233: $oUser = $oFoundUser;
234:
235: $oMin = $this->getMinModuleDecorator();
236: if ($oMin) {
237: $mHash = $oMin->GetMinByHash($InvitationLinkHash);
238: if (isset($mHash['__hash__'], $mHash['UserId']) && !isset($mHash['Registered'])) {
239: $oMin->DeleteMinByHash($mHash['__hash__']);
240: }
241: }
242: }
243: }
244: }
245:
246: /**
247: * Updates Invitation link hash in Min module for user with $aArgs['UserId'] identifier.
248: *
249: * @ignore
250: * @param array $aArgs
251: * @param mixed $mResult
252: */
253: public function onAfterCreateUser($aArgs, &$mResult)
254: {
255: $iUserId = isset($mResult) && (int) $mResult > 0 ? $mResult : 0;
256: if (0 < $iUserId) {
257: self::Decorator()->CreateInvitationLinkHash($iUserId);
258: }
259: }
260:
261: /**
262: * Deletes hash which are owened by the specified user.
263: *
264: * @ignore
265: * @param array $aArgs
266: * @param mixed $mResult
267: */
268: public function onAfterDeleteUser($aArgs, $mResult)
269: {
270: if ($mResult) {
271: $this->getMinModuleDecorator()->DeleteMinByID(
272: $this->generateMinId($aArgs['UserId'])
273: );
274: }
275: }
276: /***** private functions *****/
277:
278: /***** public functions might be called with web API *****/
279: /**
280: * Obtains list of module settings for authenticated user.
281: *
282: * @return array
283: */
284: public function GetSettings()
285: {
286: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
287:
288: return array(
289: 'RegisterModuleHash' => $this->getRegisterModuleHash(),
290: 'RegisterModuleName' => $this->oModuleSettings->RegisterModuleName,
291: 'LoginModuleHash' => $this->getLoginModuleHash(),
292: 'EnableSendInvitationLinkViaMail' => $this->oModuleSettings->EnableSendInvitationLinkViaMail,
293: );
294: }
295:
296: /**
297: * Create Invitation link hash for specified user.
298: *
299: * @param int $UserId User identifier.
300: * @return string
301: */
302: public function CreateInvitationLinkHash($UserId)
303: {
304: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
305:
306: $mHash = '';
307: $oMin = $this->getMinModuleDecorator();
308: if ($oMin) {
309: $sMinId = $this->generateMinId($UserId);
310: $aHashData = $oMin->GetMinById($sMinId);
311: if (!$aHashData) {
312: $mHash = $oMin->CreateMin(
313: $sMinId,
314: array(
315: 'UserId' => $UserId
316: ),
317: $UserId
318: );
319: } else {
320: $mHash = $this->GetInvitationLinkHash($UserId);
321: }
322: }
323:
324: return $mHash;
325: }
326:
327: /**
328: *
329: * @param string $Email
330: * @param string $Hash
331: */
332: public function SendNotification($Email, $Hash)
333: {
334: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);
335:
336: $bEnableSendInvitation = $this->oModuleSettings->EnableSendInvitationLinkViaMail;
337: if (!$bEnableSendInvitation) {
338: return false;
339: }
340:
341: $oModuleManager = \Aurora\System\Api::GetModuleManager();
342: $sSiteName = $oModuleManager->getModuleConfigValue('Core', 'SiteName');
343: $sBody = \file_get_contents($this->GetPath() . '/templates/InvitationMail.html');
344: if (\is_string($sBody)) {
345: $sBody = \strtr($sBody, array(
346: '{{INVITATION_URL}}' => \rtrim(Application::getBaseUrl(), '\\/ ') . "/index.php#register/" . $Hash,
347: '{{SITE_NAME}}' => $sSiteName
348: ));
349: }
350: $sSubject = "You're invited to join " . $sSiteName;
351: $sFrom = $this->oModuleSettings->NotificationEmail;
352:
353: $oMail = new PHPMailer();
354:
355: $sType = $this->oModuleSettings->NotificationType;
356: if (\strtolower($sType) === 'mail') {
357: $oMail->isMail();
358: } elseif (\strtolower($sType) === 'smtp') {
359: $oMail->isSMTP();
360: $oMail->Host = $this->oModuleSettings->NotificationHost;
361: $oMail->Port = (int) $this->oModuleSettings->NotificationPort;
362: ;
363: $oMail->SMTPAuth = (bool) $this->oModuleSettings->NotificationUseAuth;
364: if ($oMail->SMTPAuth) {
365: $oMail->Username = $this->oModuleSettings->NotificationLogin;
366: $oMail->Password = $this->oModuleSettings->NotificationPassword;
367: }
368: $oMail->SMTPOptions = array(
369: 'ssl' => array(
370: 'verify_peer' => false,
371: 'verify_peer_name' => false,
372: 'allow_self_signed' => true
373: )
374: );
375: $sSmtpSecure = $this->oModuleSettings->SMTPSecure;
376: if (!empty($sSmtpSecure)) {
377: $oMail->SMTPSecure = $sSmtpSecure;
378: }
379: }
380:
381: $oMail->setFrom($sFrom);
382: $oMail->addAddress($Email);
383: $oMail->addReplyTo($sFrom, $sSiteName);
384:
385: $oMail->isHTML(true); // Set email format to HTML
386:
387: $oMail->Subject = $sSubject;
388: $oMail->Body = $sBody;
389:
390: return $oMail->send();
391: }
392:
393: /**
394: * Returns Invitation link hash for specified user.
395: *
396: * @param int $UserId User identifier.
397: * @return string
398: */
399: public function GetInvitationLinkHash($UserId)
400: {
401: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
402:
403: $oUser = \Aurora\Modules\Core\Module::Decorator()->GetUserWithoutRoleCheck($UserId);
404: $oAuthenticatedUser = \Aurora\System\Api::getAuthenticatedUser();
405: $bAllowHash = false;
406: if ($oAuthenticatedUser && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::TenantAdmin && $oUser && $oUser->IdTenant === $oAuthenticatedUser->IdTenant) {
407: $bAllowHash = true;
408: } elseif ($oAuthenticatedUser && $oAuthenticatedUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
409: $bAllowHash = true;
410: }
411:
412: if (!$bAllowHash) {
413: return '';
414: }
415:
416: $mHash = '';
417: $oMin = $this->getMinModuleDecorator();
418: if ($oMin) {
419: $sMinId = $this->generateMinId($UserId);
420: $mHash = $oMin->GetMinById($sMinId);
421:
422: if ($mHash) {
423: if (isset($mHash['__hash__']) && !isset($mHash['Registered'])) {
424: $mHash = $mHash['__hash__'];
425: } else {
426: $mHash = '';
427: }
428: }
429: }
430:
431: return $mHash;
432: }
433:
434: /**
435: * Returns public id of user obtained from Invitation link hash.
436: *
437: * @param string $InvitationLinkHash Invitation link hash with information about user and its registration status.
438: * @return string
439: */
440: public function GetUserPublicId($InvitationLinkHash)
441: {
442: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
443:
444: $oUser = $this->getUserByInvitationLinkHash($InvitationLinkHash);
445: if ($oUser) {
446: return $oUser->PublicId;
447: }
448: return '';
449: }
450: /***** public functions might be called with web API *****/
451: }
452: