1: | <?php |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | namespace Aurora\Modules\TeamContacts; |
9: | |
10: | use Afterlogic\DAV\Backend; |
11: | use Afterlogic\DAV\Constants; |
12: | use Aurora\Api; |
13: | use Aurora\Modules\Contacts\Enums\StorageType; |
14: | use Aurora\Modules\Contacts\Models\ContactCard; |
15: | use Aurora\Modules\Contacts\Module as ContactsModule; |
16: | use Aurora\System\Enums\UserRole; |
17: | use Sabre\VObject\UUIDUtil; |
18: | use Illuminate\Database\Capsule\Manager as Capsule; |
19: | use Aurora\System\Exceptions\ApiException; |
20: | use Aurora\Modules\Contacts\Enums\Access; |
21: | |
22: | |
23: | |
24: | |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | class Module extends \Aurora\System\Module\AbstractModule |
32: | { |
33: | protected static $iStorageOrder = 20; |
34: | |
35: | protected $userPublicIdToDelete = null; |
36: | |
37: | protected $teamAddressBook = null; |
38: | |
39: | protected $storagesMapToAddressbooks = [ |
40: | StorageType::Team => Constants::ADDRESSBOOK_TEAM_NAME |
41: | ]; |
42: | |
43: | public function init() |
44: | { |
45: | $this->subscribeEvent('Contacts::GetAddressBooks::after', array($this, 'onAfterGetAddressBooks')); |
46: | $this->subscribeEvent('Core::CreateUser::after', array($this, 'onAfterCreateUser')); |
47: | $this->subscribeEvent('Contacts::PrepareFiltersFromStorage', array($this, 'onPrepareFiltersFromStorage')); |
48: | $this->subscribeEvent('Contacts::GetContacts::after', array($this, 'onAfterGetContacts')); |
49: | $this->subscribeEvent('Contacts::GetContact::after', array($this, 'onAfterGetContact')); |
50: | $this->subscribeEvent('Core::DoServerInitializations::after', array($this, 'onAfterDoServerInitializations')); |
51: | $this->subscribeEvent('Contacts::CheckAccessToObject::after', array($this, 'onAfterCheckAccessToObject')); |
52: | $this->subscribeEvent('Contacts::GetContactSuggestions', array($this, 'onGetContactSuggestions')); |
53: | $this->subscribeEvent('Contacts::CheckAccessToAddressBook::after', array($this, 'onAfterCheckAccessToAddressBook')); |
54: | $this->subscribeEvent('Contacts::UpdateAddressBook::before', array($this, 'onBeforeUpdateAddressBook')); |
55: | |
56: | $this->subscribeEvent('Contacts::PopulateContactArguments', array($this, 'populateContactArguments')); |
57: | $this->subscribeEvent('Contacts::CreateContact::before', array($this, 'populateContactArguments')); |
58: | $this->subscribeEvent('Contacts::ContactQueryBuilder', array($this, 'onContactQueryBuilder')); |
59: | |
60: | $this->subscribeEvent('Core::DeleteUser::before', array($this, 'onBeforeDeleteUser')); |
61: | $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser')); |
62: | |
63: | $this->subscribeEvent('Contacts::UpdateContactObject::before', array($this, 'onBeforeUpdateContactObject')); |
64: | $this->subscribeEvent('Contacts::GetStoragesMapToAddressbooks::after', array($this, 'onAfterGetStoragesMapToAddressbooks')); |
65: | |
66: | $this->subscribeEvent('Core::GetGroupContactsEmails', array($this, 'onGetGroupContactsEmails')); |
67: | } |
68: | |
69: | |
70: | |
71: | |
72: | public static function getInstance() |
73: | { |
74: | return parent::getInstance(); |
75: | } |
76: | |
77: | |
78: | |
79: | |
80: | public static function Decorator() |
81: | { |
82: | return parent::Decorator(); |
83: | } |
84: | |
85: | |
86: | |
87: | |
88: | public function getModuleSettings() |
89: | { |
90: | return $this->oModuleSettings; |
91: | } |
92: | |
93: | public function onAfterGetAddressBooks(&$aArgs, &$mResult) |
94: | { |
95: | if (!is_array($mResult)) { |
96: | $mResult = []; |
97: | } |
98: | |
99: | $addressbook = $this->GetTeamAddressbook($aArgs['UserId']); |
100: | if ($addressbook) { |
101: | |
102: | |
103: | |
104: | $mResult[] = [ |
105: | 'Id' => 'team', |
106: | 'EntityId' => (int) $addressbook['id'], |
107: | 'CTag' => (int) $addressbook['{http://sabredav.org/ns}sync-token'], |
108: | 'Display' => true, |
109: | 'Order' => 1, |
110: | 'DisplayName' => $addressbook['{DAV:}displayname'], |
111: | 'Uri' => $addressbook['uri'], |
112: | 'Url' => $addressbook['uri'], |
113: | ]; |
114: | } |
115: | } |
116: | |
117: | public function GetTeamAddressbook($UserId) |
118: | { |
119: | Api::CheckAccess($UserId); |
120: | |
121: | $addressbook = false; |
122: | |
123: | $oUser = Api::getUserById($UserId); |
124: | if ($oUser) { |
125: | $sPrincipalUri = Constants::PRINCIPALS_PREFIX . $oUser->IdTenant . '_' . Constants::DAV_TENANT_PRINCIPAL; |
126: | $addressbook = Backend::Carddav()->getAddressBookForUser($sPrincipalUri, 'gab'); |
127: | if (!$addressbook) { |
128: | if (Backend::Carddav()->createAddressBook($sPrincipalUri, 'gab', ['{DAV:}displayname' => Constants::ADDRESSBOOK_TEAM_DISPLAY_NAME])) { |
129: | $addressbook = Backend::Carddav()->getAddressBookForUser($sPrincipalUri, 'gab'); |
130: | } |
131: | } |
132: | if ($addressbook) { |
133: | $addressbook['id_tenant'] = $oUser->IdTenant; |
134: | } |
135: | } |
136: | |
137: | return $addressbook; |
138: | } |
139: | |
140: | private function createContactForUser($iUserId, $sEmail) |
141: | { |
142: | $mResult = false; |
143: | if (0 < $iUserId) { |
144: | $addressbook = $this->GetTeamAddressbook($iUserId); |
145: | if ($addressbook) { |
146: | $uid = UUIDUtil::getUUID(); |
147: | $vcard = new \Sabre\VObject\Component\VCard(['UID' => $uid]); |
148: | $vcard->add( |
149: | 'EMAIL', |
150: | $sEmail, |
151: | [ |
152: | 'type' => ['work'], |
153: | 'pref' => 1, |
154: | ] |
155: | ); |
156: | |
157: | $mResult = !!Backend::Carddav()->createCard($addressbook['id'], $uid . '.vcf', $vcard->serialize()); |
158: | } |
159: | } |
160: | return $mResult; |
161: | } |
162: | |
163: | public function onAfterCreateUser($aArgs, &$mResult) |
164: | { |
165: | $iUserId = isset($mResult) && (int) $mResult > 0 ? $mResult : 0; |
166: | |
167: | if ((int) $iUserId > 0) { |
168: | $this->createContactForUser($iUserId, $aArgs['PublicId']); |
169: | } |
170: | } |
171: | |
172: | public function onPrepareFiltersFromStorage(&$aArgs, &$mResult) |
173: | { |
174: | if (isset($aArgs['Storage']) && ($aArgs['Storage'] === StorageType::Team || $aArgs['Storage'] === StorageType::All)) { |
175: | $aArgs['IsValid'] = true; |
176: | |
177: | $oUser = \Aurora\System\Api::getAuthenticatedUser(); |
178: | |
179: | $addressbook = $this->GetTeamAddressbook($oUser->Id); |
180: | if ($addressbook) { |
181: | if (isset($aArgs['Query'])) { |
182: | $aArgs['Query']->addSelect(Capsule::connection()->raw( |
183: | 'CASE |
184: | WHEN ' . Capsule::connection()->getTablePrefix() . 'adav_cards.addressbookid = ' . $addressbook['id'] . ' THEN true |
185: | ELSE false |
186: | END as IsTeam' |
187: | )); |
188: | } |
189: | $mResult = $mResult->orWhere('adav_cards.addressbookid', $addressbook['id']); |
190: | } |
191: | } |
192: | } |
193: | |
194: | public function onAfterGetContacts($aArgs, &$mResult) |
195: | { |
196: | if (\is_array($mResult) && \is_array($mResult['List'])) { |
197: | $user = Api::getUserById($aArgs['UserId']); |
198: | $teamAddressbook = $this->GetTeamAddressbook($aArgs['UserId']); |
199: | |
200: | if ($user && $teamAddressbook) { |
201: | $authenticatedUser = \Aurora\System\Api::getAuthenticatedUser(); |
202: | foreach ($mResult['List'] as $iIndex => $aContact) { |
203: | $allowEditTeamContactsByTenantAdmins = ContactsModule::getInstance()->oModuleSettings->AllowEditTeamContactsByTenantAdmins; |
204: | $isUserTenantAdmin = $authenticatedUser->Role === UserRole::TenantAdmin && $user->IdTenant === $authenticatedUser->IdTenant; |
205: | |
206: | if (isset($aContact['AddressBookId']) && $aContact['AddressBookId'] == $teamAddressbook['id']) { |
207: | $aContact['ReadOnly'] = false; |
208: | if ($aContact['ViewEmail'] === $user->PublicId) { |
209: | $aContact['ItsMe'] = true; |
210: | } elseif (!(($allowEditTeamContactsByTenantAdmins && $isUserTenantAdmin) || $authenticatedUser->isAdmin())) { |
211: | $aContact['ReadOnly'] = true; |
212: | } |
213: | $mResult['List'][$iIndex] = $aContact; |
214: | } |
215: | } |
216: | } |
217: | } |
218: | } |
219: | |
220: | public function onAfterGetContact($aArgs, &$mResult) |
221: | { |
222: | $authenticatedUser = \Aurora\System\Api::getAuthenticatedUser(); |
223: | $teamAddressbook = $this->GetTeamAddressbook($authenticatedUser->Id); |
224: | if ($teamAddressbook) { |
225: | if ($mResult && $authenticatedUser && $mResult->AddressBookId == $teamAddressbook['id']) { |
226: | $mResult->IdTenant = $teamAddressbook['id_tenant'] ?? 0; |
227: | $allowEditTeamContactsByTenantAdmins = ContactsModule::getInstance()->oModuleSettings->AllowEditTeamContactsByTenantAdmins; |
228: | $isUserTenantAdmin = $authenticatedUser->Role === UserRole::TenantAdmin; |
229: | $isContactInTenant = $mResult->IdTenant === $authenticatedUser->IdTenant; |
230: | if ($mResult->BusinessEmail === $authenticatedUser->PublicId) { |
231: | $mResult->ExtendedInformation['ItsMe'] = true; |
232: | } elseif (!(($allowEditTeamContactsByTenantAdmins && $isUserTenantAdmin && $isContactInTenant) || $authenticatedUser->isAdmin())) { |
233: | $mResult->ExtendedInformation['ReadOnly'] = true; |
234: | } |
235: | } |
236: | } |
237: | } |
238: | |
239: | |
240: | |
241: | |
242: | public function onAfterDoServerInitializations($aArgs, &$mResult) |
243: | { |
244: | $oUser = \Aurora\System\Api::getAuthenticatedUser(); |
245: | if ($oUser && $oUser->Role === UserRole::NormalUser) { |
246: | $teamAddressBook = $this->GetTeamAddressbook($oUser->Id); |
247: | if ($teamAddressBook) { |
248: | $contact = Capsule::connection()->table('contacts_cards') |
249: | ->where('AddressBookId', $teamAddressBook['id']) |
250: | ->where('ViewEmail', $oUser->PublicId) |
251: | ->first(); |
252: | if (!$contact) { |
253: | $this->createContactForUser($oUser->Id, $oUser->PublicId); |
254: | } |
255: | } |
256: | } |
257: | } |
258: | |
259: | public function onGetContactSuggestions(&$aArgs, &$mResult) |
260: | { |
261: | if ($aArgs['Storage'] === 'all' || $aArgs['Storage'] === StorageType::Team) { |
262: | $mResult[StorageType::Team] = \Aurora\Modules\Contacts\Module::Decorator()->GetContacts( |
263: | $aArgs['UserId'], |
264: | StorageType::Team, |
265: | 0, |
266: | $aArgs['Limit'], |
267: | $aArgs['SortField'], |
268: | $aArgs['SortOrder'], |
269: | $aArgs['Search'] |
270: | ); |
271: | } |
272: | } |
273: | |
274: | |
275: | |
276: | |
277: | public function populateContactArguments(&$aArgs, &$mResult) |
278: | { |
279: | if (isset($aArgs['Storage'], $aArgs['UserId'])) { |
280: | $aStorageParts = \explode('-', $aArgs['Storage']); |
281: | if (isset($aStorageParts[0]) && $aStorageParts[0] === StorageType::Team) { |
282: | |
283: | $addressbook = $this->GetTeamAddressbook($aArgs['UserId']); |
284: | if ($addressbook) { |
285: | $aArgs['Storage'] = StorageType::Team; |
286: | $aArgs['AddressBookId'] = $addressbook['id']; |
287: | |
288: | $mResult = true; |
289: | } |
290: | } |
291: | } |
292: | } |
293: | |
294: | public function onContactQueryBuilder(&$aArgs, &$query) |
295: | { |
296: | $addressbook = $this->GetTeamAddressbook($aArgs['UserId']); |
297: | $query->orWhere(function ($q) use ($addressbook, $aArgs) { |
298: | $q->where('adav_addressbooks.id', $addressbook['id']); |
299: | if (is_array($aArgs['UUID'])) { |
300: | $ids = $aArgs['UUID']; |
301: | if (count($aArgs['UUID']) === 0) { |
302: | $ids = [null]; |
303: | } |
304: | $q->whereIn('adav_cards.id', $ids); |
305: | } else { |
306: | $q->where('adav_cards.id', $aArgs['UUID']); |
307: | } |
308: | }); |
309: | } |
310: | |
311: | public function onBeforeDeleteUser(&$aArgs, &$mResult) |
312: | { |
313: | if (isset($aArgs['UserId'])) { |
314: | $this->userPublicIdToDelete = Api::getUserPublicIdById($aArgs['UserId']); |
315: | $this->teamAddressBook = $this->GetTeamAddressbook($aArgs['UserId']); |
316: | } |
317: | } |
318: | |
319: | public function onAfterDeleteUser($aArgs, &$mResult) |
320: | { |
321: | if ($mResult && $this->userPublicIdToDelete && $this->teamAddressBook) { |
322: | $card = Capsule::connection()->table('contacts_cards') |
323: | ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id') |
324: | ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id') |
325: | ->where('adav_addressbooks.id', $this->teamAddressBook['id']) |
326: | ->where('ViewEmail', $this->userPublicIdToDelete) |
327: | ->select('adav_cards.uri as card_uri', 'adav_addressbooks.id as addressbook_id') |
328: | ->first(); |
329: | |
330: | if ($card) { |
331: | Backend::Carddav()->deleteCard($card->addressbook_id, $card->card_uri); |
332: | } |
333: | } |
334: | } |
335: | |
336: | public function onAfterCheckAccessToAddressBook(&$aArgs, &$mResult) |
337: | { |
338: | if (isset($aArgs['User'], $aArgs['AddressBookId'])) { |
339: | $oUser = $aArgs['User']; |
340: | $addressbook = $this->GetTeamAddressbook($oUser->Id); |
341: | if ($addressbook && $addressbook['id'] == $aArgs['AddressBookId']) { |
342: | if ($aArgs['Access'] === Access::Write && $oUser->UserRole !== UserRole::SuperAdmin) { |
343: | if ($oUser->UserRole === UserRole::TenantAdmin && ContactsModule::getInstance()->oModuleSettings->AllowEditTeamContactsByTenantAdmins) { |
344: | $mResult = true; |
345: | } else { |
346: | $mResult = false; |
347: | } |
348: | } else { |
349: | $mResult = true; |
350: | } |
351: | return true; |
352: | } |
353: | } |
354: | } |
355: | |
356: | public function onAfterCheckAccessToObject(&$aArgs, &$mResult) |
357: | { |
358: | $oUser = $aArgs['User'] ?? null; |
359: | $oContact = $aArgs['Contact'] ?? null; |
360: | |
361: | if ($oUser) { |
362: | $teamAddressBook = $this->GetTeamAddressbook($oUser->Id); |
363: | if ($oContact instanceof \Aurora\Modules\Contacts\Classes\Contact && (int) $oContact->AddressBookId === (int) $teamAddressBook['id']) { |
364: | if ($aArgs['Access'] === Access::Write && $aArgs['User']->UserRole !== UserRole::SuperAdmin) { |
365: | if ((isset($oContact->ExtendedInformation['ItsMe']) && $oContact->ExtendedInformation['ItsMe']) || |
366: | ($oUser->Role === UserRole::TenantAdmin && ContactsModule::getInstance()->oModuleSettings->AllowEditTeamContactsByTenantAdmins)) { |
367: | $mResult = true; |
368: | } else { |
369: | $mResult = false; |
370: | } |
371: | } else { |
372: | $mResult = true; |
373: | } |
374: | return true; |
375: | } |
376: | } |
377: | } |
378: | |
379: | public function onBeforeUpdateContactObject(&$aArgs, &$mResult) |
380: | { |
381: | $user = Api::getAuthenticatedUser(); |
382: | $oContact = $aArgs['Contact'] ?? null; |
383: | |
384: | if ($user && $oContact) { |
385: | $addressbook = Backend::Carddav()->getAddressBookById($oContact->AddressBookId); |
386: | if ($addressbook['uri'] === 'gab') { |
387: | $teamAddressbook = $this->GetTeamAddressbook($user->Id); |
388: | |
389: | $isSuperAdmin = $user->Role === UserRole::SuperAdmin; |
390: | $isTenant = $user->Role === UserRole::TenantAdmin; |
391: | $isCorrectTeamAddressbook = $teamAddressbook['id'] == $oContact->AddressBookId; |
392: | $isItsMe = isset($oContact->ExtendedInformation['ItsMe']) && $oContact->ExtendedInformation['ItsMe']; |
393: | $isReadOnly = isset($oContact->ExtendedInformation['ReadOnly']) && $oContact->ExtendedInformation['ReadOnly']; |
394: | |
395: | if (!($isSuperAdmin || ($isTenant && !$isReadOnly && $isCorrectTeamAddressbook) || $isItsMe)) { |
396: | throw new ApiException(\Aurora\System\Notifications::AccessDenied, null, 'AccessDenied'); |
397: | } |
398: | } |
399: | } |
400: | } |
401: | |
402: | public function onBeforeUpdateAddressBook(&$aArgs, &$mResult) |
403: | { |
404: | $addressbook = Backend::Carddav()->getAddressBookById($aArgs['EntityId']); |
405: | if ($addressbook && $addressbook['uri'] === 'gab') { |
406: | throw new ApiException(\Aurora\System\Notifications::AccessDenied, null, 'AccessDenied'); |
407: | } |
408: | } |
409: | |
410: | public function onAfterGetStoragesMapToAddressbooks(&$aArgs, &$mResult) |
411: | { |
412: | $mResult = array_merge($mResult, $this->storagesMapToAddressbooks); |
413: | } |
414: | |
415: | public function onGetGroupContactsEmails(&$aArgs, &$mResult) |
416: | { |
417: | $oUser = $aArgs['User']; |
418: | $oGroup = $aArgs['Group']; |
419: | if ($oUser && $oGroup) { |
420: | $abook = $this->GetTeamAddressbook($oUser->Id); |
421: | if ($abook) { |
422: | if ($oGroup->IsAll) { |
423: | $mResult = ContactCard::where('AddressBookId', $abook['id'])->get()->map( |
424: | function (ContactCard $oContact) { |
425: | if (!empty($oContact->FullName)) { |
426: | return '"' . $oContact->FullName . '"' . '<' . $oContact->ViewEmail . '>'; |
427: | } else { |
428: | return $oContact->ViewEmail; |
429: | } |
430: | } |
431: | )->toArray(); |
432: | } else { |
433: | $mResult = $oGroup->Users->map(function ($oUser) use ($abook) { |
434: | $oContact = ContactCard::where('IdUser', $oUser->Id)->where('AddressBookId', $abook['id'])->first(); |
435: | if ($oContact && !empty($oContact->FullName)) { |
436: | return '"' . $oContact->FullName . '"' . '<' . $oUser->PublicId . '>'; |
437: | } else { |
438: | return $oUser->PublicId; |
439: | } |
440: | })->toArray(); |
441: | } |
442: | } |
443: | } |
444: | } |
445: | } |
446: | |