1: <?php
2: /**
3: * This code is licensed under Afterlogic Software License.
4: * For full statements of the license see LICENSE file.
5: */
6:
7: namespace Aurora\Modules\SharedContacts;
8:
9: use Afterlogic\DAV\Backend;
10: use Afterlogic\DAV\Constants;
11: use Aurora\Api;
12: use Aurora\Modules\Contacts\Enums\Access;
13: use Aurora\Modules\Contacts\Enums\SortField;
14: use Aurora\Modules\Contacts\Enums\StorageType;
15: use Aurora\Modules\Contacts\Classes\Contact;
16: use Aurora\Modules\Contacts\Module as ContactsModule;
17: use Aurora\Modules\Core\Models\User;
18: use Aurora\System\Enums\UserRole;
19: use Aurora\System\Exceptions\InvalidArgumentException;
20: use Aurora\System\Notifications;
21: use Illuminate\Database\Capsule\Manager as Capsule;
22: use Sabre\DAV\UUIDUtil;
23: use Aurora\Modules\Core\Module as CoreModule;
24:
25: /**
26: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
27: * @copyright Copyright (c) 2023, Afterlogic Corp.
28: *
29: * @property Settings $oModuleSettings
30: *
31: * @package Modules
32: */
33: class Module extends \Aurora\System\Module\AbstractModule
34: {
35: protected static $iStorageOrder = 10;
36:
37: protected $oBeforeDeleteUser = null;
38:
39: protected $storagesMapToAddressbooks = [
40: StorageType::Shared => Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME
41: ];
42:
43: public function init()
44: {
45: $this->subscribeEvent('Contacts::PrepareFiltersFromStorage', array($this, 'onPrepareFiltersFromStorage'));
46: $this->subscribeEvent('Contacts::UpdateSharedContacts::after', array($this, 'onAfterUpdateSharedContacts'));
47:
48: $this->subscribeEvent('Contacts::CheckAccessToObject::after', array($this, 'onAfterCheckAccessToObject'));
49: $this->subscribeEvent('Contacts::GetContactSuggestions', array($this, 'onGetContactSuggestions'));
50: $this->subscribeEvent('Contacts::GetAddressBooks::after', array($this, 'onAfterGetAddressBooks'), 1000);
51: // $this->subscribeEvent('Contacts::PopulateContactModel', array($this, 'onPopulateContactModel'));
52:
53: $this->subscribeEvent('Contacts::ContactQueryBuilder', array($this, 'onContactQueryBuilder'));
54: $this->subscribeEvent('Contacts::CreateContact::before', array($this, 'onBeforeCreateContact'));
55: $this->subscribeEvent('Contacts::CheckAccessToAddressBook::after', array($this, 'onAfterCheckAccessToAddressBook'));
56:
57: $this->subscribeEvent(self::GetName() . '::UpdateAddressbookShare::before', array($this, 'onBeforeUpdateAddressbookShare'));
58: $this->subscribeEvent(self::GetName() . '::GetSharesForAddressbook::before', array($this, 'onBeforeUpdateAddressbookShare'));
59: $this->subscribeEvent(self::GetName() . '::LeaveShare::before', array($this, 'onBeforeUpdateAddressbookShare'));
60:
61: $this->subscribeEvent('Contacts::GetContacts::before', array($this, 'populateContactArguments'));
62: $this->subscribeEvent('Contacts::GetContactsByEmails::before', array($this, 'populateContactArguments'));
63: $this->subscribeEvent('Contacts::GetContacts::after', array($this, 'onGetContacts'));
64: $this->subscribeEvent('Contacts::GetContactsByUids::after', array($this, 'onGetContactsByUids'));
65: $this->subscribeEvent('Contacts::PopulateContactArguments', array($this, 'populateContactArguments'));
66: $this->subscribeEvent('Contacts::DeleteContacts::before', array($this, 'onBeforeDeleteContacts'));
67: $this->subscribeEvent('Contacts::GetStoragesMapToAddressbooks::after', array($this, 'onAfterGetStoragesMapToAddressbooks'));
68: $this->subscribeEvent('Contacts::Export::before', array($this, 'populateContactArguments'));
69:
70: $this->subscribeEvent('Core::AddUsersToGroup::after', [$this, 'onAfterAddUsersToGroup']);
71: $this->subscribeEvent('Core::RemoveUsersFromGroup::after', [$this, 'onAfterRemoveUsersFromGroup']);
72: $this->subscribeEvent('Core::CreateUser::after', [$this, 'onAfterCreateUser']);
73: $this->subscribeEvent('Core::UpdateUser::after', [$this, 'onAfterUpdateUser']);
74: $this->subscribeEvent('Core::DeleteUser::before', [$this, 'onBeforeDeleteUser']);
75: $this->subscribeEvent('Core::DeleteUser::after', [$this, 'onAfterDeleteUser']);
76: $this->subscribeEvent('Core::DeleteGroup::after', [$this, 'onAfterDeleteGroup']);
77: }
78:
79: /**
80: * @return Module
81: */
82: public static function getInstance()
83: {
84: return parent::getInstance();
85: }
86:
87: /**
88: * @return Module
89: */
90: public static function Decorator()
91: {
92: return parent::Decorator();
93: }
94:
95: /**
96: * @return Settings
97: */
98: public function getModuleSettings()
99: {
100: return $this->oModuleSettings;
101: }
102:
103: public function GetAddressbooks($UserId)
104: {
105: $mResult = [];
106:
107: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
108: Api::CheckAccess($UserId);
109:
110: $dBPrefix = Api::GetSettings()->DBPrefix;
111: $stmt = Api::GetPDO()->prepare("
112: select ab.*, sab.access, sab.group_id, sab.addressbookuri from " . $dBPrefix . "adav_shared_addressbooks sab
113: left join " . $dBPrefix . "adav_addressbooks ab on sab.addressbook_id = ab.id
114: where sab.principaluri = ?
115: ");
116:
117: $principalUri = Constants::PRINCIPALS_PREFIX . Api::getUserPublicIdById($UserId);
118: $stmt->execute([
119: $principalUri
120: ]);
121:
122: $abooks = $stmt->fetchAll(\PDO::FETCH_ASSOC);
123:
124: foreach ($abooks as $abook) {
125: if ($abook['principaluri'] !== $principalUri) {
126: if (isset($abook['id'])) {
127: $storage = StorageType::Shared . '-' . $abook['id'];
128: } else {
129: $storage = StorageType::Shared . '-' . StorageType::Personal;
130: }
131:
132: if (count($mResult) > 0) {
133: foreach ($mResult as $key => $val) {
134: if ($val['Id'] === $storage) {
135: $iAccess = $mResult[$key]['Access'];
136: if ($val['GroupId'] === 0 && $iAccess === Access::NoAccess) {
137: continue 2;
138: }
139: $iNewAccess = $abook['access'];
140: if ((int) $abook['group_id'] === 0) { //personal sharing
141: $mResult[$key]['Access'] = $iNewAccess;
142: } else { // group sharing
143: if ($iNewAccess !== Access::Read) {
144: if ($iAccess < $iNewAccess || $iNewAccess === Access::NoAccess) {
145: $mResult[$key]['Access'] = $iNewAccess;
146: }
147: } elseif ($iAccess !== Access::Write) {
148: $mResult[$key]['Access'] = $iNewAccess;
149: }
150: }
151: continue 2;
152: }
153: }
154: }
155:
156: $prevState = Api::skipCheckUserRole(true);
157: Api::skipCheckUserRole($prevState);
158:
159: $mResult[] = [
160: 'Id' => $storage,
161: 'EntityId' => isset($abook['id']) ? (int) $abook['id'] : null,
162: 'CTag' => isset($abook['synctoken']) ? (int) $abook['synctoken'] : 0,
163: 'Display' => true,
164: 'Order' => 1,
165: 'DisplayName' => $abook['displayname'] . ' (' . basename($abook['principaluri']) . ')',
166: 'Uri' => $abook['addressbookuri'],
167: 'Url' => 'addressbooks/' . $abook['addressbookuri'],
168: 'Shared' => true,
169: 'Access' => (int) $abook['access'],
170: 'Owner' => basename($abook['principaluri']),
171: 'GroupId' => (int) $abook['group_id']
172: ];
173: }
174: }
175:
176: return array_filter($mResult, function ($item) {
177: return ($item['Access'] !== Access::NoAccess);
178: });
179: }
180:
181: public function GetSharesForAddressbook($UserId, $Id)
182: {
183: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
184: Api::CheckAccess($UserId);
185:
186: $aResult = [];
187:
188: $shares = $this->_getSharesForAddressbook($UserId, $Id);
189: if (count($shares) > 0) {
190: $oUser = Api::getUserById($UserId);
191: $groups = [];
192: foreach ($shares as $share) {
193: if ($share['group_id'] != 0) {
194: if (!in_array($share['group_id'], $groups)) {
195: $oGroup = CoreModule::Decorator()->GetGroup((int) $share['group_id']);
196: if ($oGroup) {
197: $groups[] = $share['group_id'];
198: $aResult[] = [
199: 'PublicId' => $oGroup->getName(),
200: 'Access' => (int) $share['access'],
201: 'IsGroup' => true,
202: 'IsAll' => !!$oGroup->IsAll,
203: 'GroupId' => (int) $share['group_id']
204: ];
205: }
206: }
207: } else {
208: $aResult[] = [
209: 'PublicId' => basename($share['principaluri']),
210: 'Access' => (int) $share['access']
211: ];
212: }
213: }
214: }
215:
216: return $aResult;
217: }
218:
219: protected function _getSharesForAddressbook($iUserId, $abookComplexId)
220: {
221: $dBPrefix = Api::GetSettings()->DBPrefix;
222: $stmt = Api::GetPDO()->prepare("
223: select sab.* from " . $dBPrefix . "adav_shared_addressbooks sab
224: left join " . $dBPrefix . "adav_addressbooks ab on sab.addressbook_id = ab.id
225: left join " . $dBPrefix . "core_users cu on ab.principaluri = CONCAT('principals/', cu.PublicId)
226: where cu.Id = ? AND ab.id = ?
227: ");
228:
229: $stmt->execute([
230: $iUserId,
231: $abookComplexId
232: ]);
233:
234: return $stmt->fetchAll(\PDO::FETCH_ASSOC);
235: }
236:
237: protected function getShareForAddressbook($iUserId, $abookComplexId, $principalUri, $groupId = 0)
238: {
239: $dBPrefix = Api::GetSettings()->DBPrefix;
240: $stmt = Api::GetPDO()->prepare("
241: select sab.*
242: from " . $dBPrefix . "adav_shared_addressbooks sab
243: left join " . $dBPrefix . "adav_addressbooks ab on sab.addressbook_id = ab.id
244: left join " . $dBPrefix . "core_users cu on ab.principaluri = CONCAT('principals/', cu.PublicId)
245: where cu.Id = ? and sab.principaluri = ? and sab.group_id = ? and ab.id = ?
246: ");
247:
248: $stmt->execute([
249: $iUserId,
250: $principalUri,
251: $groupId,
252: $abookComplexId
253: ]);
254:
255: return $stmt->fetchAll(\PDO::FETCH_ASSOC);
256: }
257:
258: protected function deleteShareByPublicIds($userId, $abookComplexId, $publicIds)
259: {
260: $dBPrefix = Api::GetSettings()->DBPrefix;
261:
262: $sharesIds = [];
263: foreach ($publicIds as $publicId) {
264: $publicId = \json_decode($publicId);
265: $shares = $this->getShareForAddressbook($userId, $abookComplexId, Constants::PRINCIPALS_PREFIX . $publicId[0], $publicId[1]);
266: if (is_array($shares) && count($shares) > 0) {
267: $ids = array_map(function ($share) {
268: return $share['id'];
269: }, $shares);
270: $sharesIds = array_merge($sharesIds, $ids);
271: }
272: }
273: if (count($sharesIds) > 0) {
274: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where id in (" . \implode(',', $sharesIds) . ")");
275: $stmt->execute();
276: }
277: }
278:
279: protected function getAddressbook($iUserId, $abookId)
280: {
281: $mResult = false;
282:
283: $dBPrefix = Api::GetSettings()->DBPrefix;
284:
285: $userPublicId = Api::getUserPublicIdById($iUserId);
286: if (!empty($abookId) && $userPublicId) {
287: $stmt = Api::GetPDO()->prepare("select * from " . $dBPrefix . "adav_addressbooks where principaluri = ? and id = ?");
288: $stmt->execute([Constants::PRINCIPALS_PREFIX . $userPublicId, $abookId]);
289: $mResult = $stmt->fetch(\PDO::FETCH_ASSOC);
290: }
291:
292: return $mResult;
293: }
294:
295: protected function createShare($iUserId, $abookComplexId, $share)
296: {
297: $dBPrefix = Api::GetSettings()->DBPrefix;
298:
299: $book = $this->getAddressbook($iUserId, $abookComplexId);
300: if ($book) {
301: $shareePublicId = $share['PublicId'];
302: $access = $share['Access'];
303: $groupId = $share['GroupId'];
304: $stmt = Api::GetPDO()->prepare("insert into " . $dBPrefix . "adav_shared_addressbooks
305: (principaluri, access, addressbook_id, addressbookuri, group_id)
306: values (?, ?, ?, ?, ?)");
307: $stmt->execute([Constants::PRINCIPALS_PREFIX . $shareePublicId, $access, $book['id'], UUIDUtil::getUUID(), $groupId]);
308: }
309: }
310:
311: protected function updateShare($iUserId, $abookComplexId, $share)
312: {
313: $dBPrefix = Api::GetSettings()->DBPrefix;
314: $book = $this->getAddressbook($iUserId, $abookComplexId);
315: if ($book) {
316: $shareePublicId = $share['PublicId'];
317: $access = $share['Access'];
318: $groupId = $share['GroupId'];
319: $stmt = Api::GetPDO()->prepare("update " . $dBPrefix . "adav_shared_addressbooks
320: set access = ? where principaluri = ? and addressbook_id = ? and group_id = ?");
321: $stmt->execute([$access, Constants::PRINCIPALS_PREFIX . $shareePublicId, $book['id'], $groupId]);
322: }
323: }
324:
325: public function onPrepareFiltersFromStorage(&$aArgs, &$mResult)
326: {
327: if (isset($aArgs['Storage']) && ($aArgs['Storage'] === StorageType::Shared || $aArgs['Storage'] === StorageType::All)) {
328: $oUser = Api::getUserById($aArgs['UserId']);
329: $aArgs['IsValid'] = true;
330:
331: $query = Capsule::connection()->table('adav_shared_addressbooks')
332: ->select('addressbook_id')
333: ->from('adav_shared_addressbooks')
334: ->leftJoin('adav_addressbooks', 'adav_shared_addressbooks.addressbook_id', '=', 'adav_addressbooks.id')
335: ->where('adav_addressbooks.principaluri', '<>', Constants::PRINCIPALS_PREFIX . $oUser->PublicId)
336: ->where('adav_shared_addressbooks.principaluri', Constants::PRINCIPALS_PREFIX . $oUser->PublicId);
337:
338: if ($aArgs['Storage'] === StorageType::Shared && isset($aArgs['AddressBookId'])) {
339: $query->where('addressbook_id', (int) $aArgs['AddressBookId']);
340: }
341: $ids = $query->pluck('addressbook_id')->all();
342:
343: if ($aArgs['Storage'] === StorageType::All && empty($aArgs['AddressBookId'])) {
344: $addressbook = $this->GetSharedWithAllAddressbook($oUser->Id);
345: if ($addressbook) {
346: $ids[] = (int) $addressbook['id'];
347: }
348: }
349:
350: if ($ids) {
351: $mResult->whereIn('adav_cards.addressbookid', $ids, 'or');
352: }
353:
354: if (isset($aArgs['Query'])) {
355: if ($ids) {
356: $aArgs['Query']->addSelect(Capsule::connection()->raw(
357: '
358: CASE
359: WHEN ' . Capsule::connection()->getTablePrefix() . 'adav_cards.addressbookid IN (' . implode(',', $ids) . ') THEN true
360: ELSE false
361: END as Shared'
362: ));
363: } else {
364: $aArgs['Query']->addSelect(Capsule::connection()->raw(
365: 'false as Shared'
366: ));
367: }
368: }
369: }
370: }
371:
372: public function onAfterUpdateSharedContacts($aArgs, &$mResult)
373: {
374: $oContacts = ContactsModule::Decorator();
375: $aUUIDs = isset($aArgs['UUIDs']) ? $aArgs['UUIDs'] : [];
376:
377: foreach ($aUUIDs as $sUUID) {
378: $oContact = $oContacts->GetContact($sUUID, $aArgs['UserId']);
379: if ($oContact instanceof Contact) {
380: $FromStorage = $oContact->Storage;
381: $ToStorage = $FromStorage;
382: if ($oContact->Storage === StorageType::Shared) {
383: $ToStorage = StorageType::Personal;
384: } elseif ($oContact->Storage === StorageType::Personal) {
385: $ToStorage = StorageType::Shared;
386: }
387: $oContacts->MoveContactsToStorage($aArgs['UserId'], $FromStorage, $ToStorage, [$sUUID]);
388: }
389: }
390: }
391:
392: public function onAfterCheckAccessToObject(&$aArgs, &$mResult)
393: {
394: $oUser = $aArgs['User'];
395: $oContact = isset($aArgs['Contact']) ? $aArgs['Contact'] : null;
396: $Access = isset($aArgs['Access']) ? (int) $aArgs['Access'] : null;
397:
398: if ($oContact instanceof Contact) {
399: if ($oContact->IdUser === $oUser->Id) {
400: $mResult = true;
401: return true; // break other subscriptions
402: }
403: if ($oContact->Storage === StorageType::Shared) {
404: $sharedWithAllAddressBook = self::Decorator()->GetSharedWithAllAddressbook($oUser->Id);
405: if ($oUser->Role === UserRole::SuperAdmin || ($sharedWithAllAddressBook && $oContact->AddressBookId == $sharedWithAllAddressBook['id'])) {
406: $mResult = true;
407: } else {
408: $mResult = false;
409: }
410: return true; // break other subscriptions
411: } elseif ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
412: $dBPrefix = Api::GetSettings()->DBPrefix;
413: $sql = "select ab.*, sab.access, ab.id as addressbook_id, cu.Id as UserId from " . $dBPrefix . "adav_shared_addressbooks sab
414: left join " . $dBPrefix . "adav_addressbooks ab on sab.addressbook_id = ab.id
415: left join " . $dBPrefix . "core_users cu on ab.principaluri = CONCAT('principals/', cu.PublicId)
416: where sab.principaluri = ? and cu.Id = ? and ab.id = ?";
417:
418: $stmt = Api::GetPDO()->prepare($sql);
419:
420: $stmt->execute([
421: Constants::PRINCIPALS_PREFIX . $oUser->PublicId,
422: $oContact->IdUser,
423: $oContact->AddressBookId
424: ]);
425:
426: $abook = $stmt->fetch(\PDO::FETCH_ASSOC);
427: if ($abook) {
428: if ((int) $abook['access'] === Access::NoAccess) {
429: $mResult = false;
430: } elseif (isset($Access)) {
431: if ($Access === (int) $abook['access'] && $Access === Access::Write) {
432: $mResult = true;
433: } else {
434: $mResult = false;
435: }
436: } else {
437: $mResult = true;
438: }
439:
440: return true; // break other subscriptions
441: }
442: }
443: }
444: }
445:
446: public function onGetContactSuggestions(&$aArgs, &$mResult)
447: {
448: if ($aArgs['Storage'] === 'all' || $aArgs['Storage'] === StorageType::Shared) {
449: $mResult[StorageType::Shared] = ContactsModule::Decorator()->GetContacts(
450: $aArgs['UserId'],
451: StorageType::Shared,
452: 0,
453: $aArgs['Limit'],
454: $aArgs['SortField'],
455: $aArgs['SortOrder'],
456: $aArgs['Search']
457: );
458: }
459: }
460:
461: /**
462: * The methods corrects the storage type of contacts related to the shared addressbooks.
463: */
464: public function onGetContacts(&$aArgs, &$mResult)
465: {
466: if ($aArgs['Storage'] === 'all' || $aArgs['Storage'] === StorageType::Shared) {
467: $aSharedBooks = $this->GetAddressbooks($aArgs['UserId']);
468: foreach ($mResult['List'] as &$aContact) {
469: $aStorageParts = explode('-', $aContact['Storage']);
470: // only personal and custom addressbooks can be shared
471: if (in_array($aStorageParts[0], [ StorageType::Personal,StorageType::AddressBook ])) {
472: foreach ($aSharedBooks as $aBook) {
473: // check if contact's addressbook exist in list of shared with user addressbooks
474: if ($aBook['EntityId'] === $aContact['AddressBookId']) {
475: $aContact['Storage'] = StorageType::Shared . '-' . $aContact['AddressBookId'];
476: }
477: }
478: }
479: }
480: }
481: }
482:
483: /**
484: * The methods corrects the storage type of contacts related to the shared addressbooks.
485: */
486: public function onGetContactsByUids(&$aArgs, &$mResult)
487: {
488: if (is_array($mResult)) {
489: $aSharedBooks = $this->GetAddressbooks($aArgs['UserId']);
490: foreach ($mResult as &$oContact) {
491: $aStorageParts = explode('-', $oContact->Storage);
492: // only personal and custom addressbooks can be shared
493: if (in_array($aStorageParts[0], [ StorageType::Personal, StorageType::AddressBook ])) {
494: foreach ($aSharedBooks as $aBook) {
495: // check if contact's addressbook exist in list of shared with user addressbooks
496: if ($aBook['EntityId'] === $oContact->AddressBookId) {
497: $oContact->Storage = StorageType::Shared . '-' . $oContact->AddressBookId;
498: }
499: }
500: }
501: }
502: }
503: }
504:
505: public function GetSharedWithAllAddressbook($UserId)
506: {
507: Api::CheckAccess($UserId);
508:
509: $addressbook = false;
510:
511: $oUser = Api::getUserById($UserId);
512: if ($oUser) {
513: $sPrincipalUri = Constants::PRINCIPALS_PREFIX . $oUser->IdTenant . '_' . Constants::DAV_TENANT_PRINCIPAL;
514: $addressbook = Backend::Carddav()->getAddressBookForUser($sPrincipalUri, Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME);
515: if (!$addressbook) {
516: if (Backend::Carddav()->createAddressBook($sPrincipalUri, Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME, ['{DAV:}displayname' => Constants::ADDRESSBOOK_SHARED_WITH_ALL_DISPLAY_NAME])) {
517: $addressbook = Backend::Carddav()->getAddressBookForUser($sPrincipalUri, Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME);
518: }
519: }
520: }
521:
522: return $addressbook;
523: }
524:
525: public function onAfterGetAddressBooks(&$aArgs, &$mResult)
526: {
527: if (!is_array($mResult)) {
528: $mResult = [];
529: }
530: foreach ($mResult as $key => $abook) {
531: $mResult[$key]['Shares'] = self::Decorator()->GetSharesForAddressbook($aArgs['UserId'], $abook['Id']);
532: }
533: $mResult = array_merge(
534: $mResult,
535: $this->GetAddressbooks($aArgs['UserId'])
536: );
537:
538: $addressbook = $this->GetSharedWithAllAddressbook($aArgs['UserId']);
539: if ($addressbook) {
540: /**
541: * @var array $addressbook
542: */
543: $mResult[] = [
544: 'Id' => 'shared',
545: 'EntityId' => (int) $addressbook['id'],
546: 'CTag' => (int) $addressbook['{http://sabredav.org/ns}sync-token'],
547: 'Display' => true,
548: 'Order' => 1,
549: 'DisplayName' => $addressbook['{DAV:}displayname'],
550: 'Uri' => $addressbook['uri'],
551: 'Url' => 'addressbooks/' . $addressbook['uri'],
552: ];
553: }
554: }
555:
556: // @todo: it doesn't work because addressbook id never has 3 parts
557: // public function onPopulateContactModel(&$oContact, &$mResult)
558: // {
559: // if ($oContact instanceof Contact) {
560: // $aStorageParts = \explode('-', $oContact->Storage);
561: // if (is_array($aStorageParts) && count($aStorageParts) === 3 && $aStorageParts[0] === StorageType::Shared) {
562: // $abooks = $this->GetAddressbooks($oContact->IdUser);
563: // foreach ($abooks as $abook) {
564: // if ($abook['Id'] === $oContact->Storage) {
565: // if ($aStorageParts[2] === StorageType::Personal) {
566: // $oContact->Storage = StorageType::Personal;
567: // } else {
568: // $oContact->Storage = StorageType::AddressBook;
569: // $oContact->AddressBookId = (int) $aStorageParts[2];
570: // }
571:
572: // $oContact->IdUser = (int) $aStorageParts[1];
573: // break;
574: // }
575: // }
576: // }
577: // }
578: // }
579:
580: public function UpdateAddressbookShare($UserId, $Id, $Shares)
581: {
582: $mResult = true;
583:
584: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
585: Api::CheckAccess($UserId);
586:
587: if (!isset($Id) || !is_array($Shares)) {
588: throw new InvalidArgumentException("", Notifications::InvalidInputParameter);
589: }
590:
591: try {
592: $oUser = Api::getUserById($UserId);
593: $currentABookShares = $this->_getSharesForAddressbook($UserId, $Id);
594:
595: $newABookShares = [];
596: foreach ($Shares as $share) {
597: if (isset($share['GroupId'])) {
598: $aUsers = CoreModule::Decorator()->GetGroupUsers($oUser->IdTenant, (int) $share['GroupId']);
599: foreach ($aUsers as $aUser) {
600: $newABookShares[] = [
601: 'PublicId' => $aUser['PublicId'],
602: 'Access' => (int) $share['Access'],
603: 'GroupId' => (int) $share['GroupId'],
604: ];
605: }
606: } else {
607: $share['GroupId'] = 0;
608: $newABookShares[] = $share;
609: }
610: }
611:
612: $currentShares = array_map(function ($share) {
613: return \json_encode([
614: basename($share['principaluri']),
615: $share['group_id']
616: ]);
617: }, $currentABookShares);
618:
619: $newShares = array_map(function ($share) {
620: return \json_encode([
621: $share['PublicId'],
622: $share['GroupId']
623: ]);
624: }, $newABookShares);
625:
626: $sharesToDelete = array_diff($currentShares, $newShares);
627: $sharesToCreate = array_diff($newShares, $currentShares);
628: $sharesToUpdate = array_intersect($currentShares, $newShares);
629:
630: if (count($sharesToDelete) > 0) {
631: $this->deleteShareByPublicIds($UserId, $Id, $sharesToDelete);
632: }
633:
634: foreach ($newABookShares as $share) {
635: $sharePublicIdAndGroupId = \json_encode([
636: $share['PublicId'],
637: $share['GroupId']
638: ]);
639: if (in_array($sharePublicIdAndGroupId, $sharesToCreate)) {
640: $this->createShare($UserId, $Id, $share);
641: }
642: if (in_array($sharePublicIdAndGroupId, $sharesToUpdate)) {
643: $this->updateShare($UserId, $Id, $share);
644: }
645: }
646:
647: $mResult = true;
648: } catch (\Exception $oException) {
649: Api::LogException($oException);
650: $mResult = false;
651: }
652:
653: return $mResult;
654: }
655:
656: public function LeaveShare($UserId, $Id)
657: {
658: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
659: Api::CheckAccess($UserId);
660:
661: $userPublicId = Api::getUserPublicIdById($UserId);
662:
663: $sharedAddressBook = Capsule::connection()->table('adav_shared_addressbooks')
664: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
665: ->where('addressbook_id', $Id)
666: ->where('group_id', 0)
667: ->first();
668:
669: if ($sharedAddressBook) {
670: Capsule::connection()->table('adav_shared_addressbooks')
671: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
672: ->where('addressbook_id', $Id)
673: ->where('group_id', 0)
674: ->update(['access' => Access::NoAccess]);
675: } else {
676: $stmt = Api::GetPDO()->prepare("insert into " . Api::GetSettings()->DBPrefix . "adav_shared_addressbooks
677: (principaluri, access, addressbook_id, addressbookuri, group_id)
678: values (?, ?, ?, ?, ?)");
679: $stmt->execute([Constants::PRINCIPALS_PREFIX . $userPublicId, Access::NoAccess, $Id, UUIDUtil::getUUID(), 0]);
680: }
681:
682: return true;
683: }
684:
685: public function onAfterDeleteGroup($aArgs, &$mResult)
686: {
687: if ($mResult) {
688: $dBPrefix = Api::GetSettings()->DBPrefix;
689: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where group_id = ?");
690: $stmt->execute([$aArgs['GroupId']]);
691: }
692: }
693:
694: /**
695: * @ignore
696: * @param array $aArgs Arguments of event.
697: * @param mixed $mResult Is passed by reference.
698: */
699: public function onBeforeDeleteUser($aArgs, &$mResult)
700: {
701: if (isset($aArgs['UserId'])) {
702: $this->oBeforeDeleteUser = Api::getUserById($aArgs['UserId']);
703: }
704: }
705:
706: /**
707: * @ignore
708: * @param array $aArgs Arguments of event.
709: * @param mixed $mResult Is passed by reference.
710: */
711: public function onAfterDeleteUser($aArgs, $mResult)
712: {
713: if ($mResult && $this->oBeforeDeleteUser instanceof User) {
714: $dBPrefix = Api::GetSettings()->DBPrefix;
715: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where principaluri = ?");
716: $stmt->execute([Constants::PRINCIPALS_PREFIX . $this->oBeforeDeleteUser->PublicId]);
717: }
718: }
719:
720: public function onAfterAddUsersToGroup($aArgs, &$mResult)
721: {
722: if ($mResult) {
723: foreach ($aArgs['UserIds'] as $iUserId) {
724: $userPublicId = Api::getUserPublicIdById($iUserId);
725: $sUserPrincipalUri = Constants::PRINCIPALS_PREFIX . $userPublicId;
726:
727: $dBPrefix = Api::GetSettings()->DBPrefix;
728: $stmt = Api::GetPDO()->prepare("select distinct addressbook_id, access from " . $dBPrefix . "adav_shared_addressbooks where group_id = ?");
729: $stmt->execute([$aArgs['GroupId']]);
730: $shares = $stmt->fetchAll(\PDO::FETCH_ASSOC);
731: foreach ($shares as $share) {
732: if (is_array($share)) {
733: $stmt = Api::GetPDO()->prepare("insert into " . $dBPrefix . "adav_shared_addressbooks
734: (principaluri, access, addressbook_id, addressbookuri, group_id)
735: values (?, ?, ?, ?, ?)");
736: $stmt->execute([$sUserPrincipalUri, $share['access'], $share['addressbook_id'], UUIDUtil::getUUID(), $aArgs['GroupId']]);
737: }
738: }
739: }
740: }
741: }
742:
743: public function onAfterCreateUser($aArgs, &$mResult)
744: {
745: if ($mResult) {
746: $oUser = User::find($mResult);
747: if ($oUser) {
748: $oGroup = CoreModule::getInstance()->GetAllGroup($oUser->IdTenant);
749: if ($oGroup) {
750: $newArgs = [
751: 'GroupId' => $oGroup->Id,
752: 'UserIds' => [$mResult]
753: ];
754: $newResult = true;
755: $this->onAfterAddUsersToGroup($newArgs, $newResult);
756: }
757: }
758: }
759: }
760:
761: public function onAfterUpdateUser($aArgs, &$mResult)
762: {
763: if ($mResult) {
764: $groupIds = $aArgs['GroupIds'] ?? null;
765: $userId = $aArgs['UserId'];
766:
767: if ($groupIds !== null) {
768: $userPublicId = Api::getUserPublicIdById($userId);
769: $sUserPrincipalUri = Constants::PRINCIPALS_PREFIX . $userPublicId;
770:
771: $dBPrefix = Api::GetSettings()->DBPrefix;
772: $stmt = Api::GetPDO()->prepare("select * from " . $dBPrefix . "adav_shared_addressbooks where group_id <> 0 and principaluri = ?");
773: $stmt->execute([$sUserPrincipalUri]);
774: $shares = $stmt->fetchAll(\PDO::FETCH_ASSOC);
775:
776: $currentGroupsIds = [];
777: if (is_array($shares)) {
778: $currentGroupsIds = array_map(function ($share) {
779: return $share['group_id'];
780: }, $shares);
781: }
782:
783: $groupsIdsToDelete = array_diff($currentGroupsIds, $groupIds);
784: $groupsIdsToCreate = array_diff($groupIds, $currentGroupsIds);
785:
786: if (count($groupsIdsToDelete) > 0) {
787: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks
788: where group_id in (" . \implode(',', $groupsIdsToDelete) . ") and principaluri = ?");
789: $stmt->execute([$sUserPrincipalUri]);
790: }
791:
792: if (count($groupsIdsToCreate) > 0) {
793: $stmt = Api::GetPDO()->prepare("select distinct addressbook_id, access, group_id from " . $dBPrefix . "adav_shared_addressbooks where group_id in (" . \implode(',', $groupsIdsToCreate) . ")");
794: $stmt->execute();
795: $shares = $stmt->fetchAll(\PDO::FETCH_ASSOC);
796: foreach ($shares as $share) {
797: if (is_array($share)) {
798: $stmt = Api::GetPDO()->prepare("insert into " . $dBPrefix . "adav_shared_addressbooks
799: (principaluri, access, addressbook_id, addressbookuri, group_id)
800: values (?, ?, ?, ?, ?)");
801: $stmt->execute([$sUserPrincipalUri, $share['access'], $share['addressbook_id'], UUIDUtil::getUUID(), $share['group_id']]);
802: }
803: }
804: }
805: }
806: }
807: }
808:
809: public function onAfterRemoveUsersFromGroup($aArgs, &$mResult)
810: {
811: if ($mResult) {
812: $principals = [];
813: foreach ($aArgs['UserIds'] as $iUserId) {
814: $oUser = Api::getUserById($iUserId);
815: $principals[] = Constants::PRINCIPALS_PREFIX . $oUser->PublicId;
816: }
817:
818: if (count($principals) > 0) {
819: $dBPrefix = Api::GetSettings()->DBPrefix;
820: $principals = array_map(function ($principal) {
821: return \Illuminate\Support\Str::padBoth($principal, strlen($principal) + 2, "'");
822: }, $principals);
823: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where principaluri in (" . \implode(',', $principals) . ") and group_id = ?");
824: $stmt->execute([$aArgs['GroupId']]);
825: }
826: }
827: }
828:
829: public function onContactQueryBuilder(&$aArgs, &$query)
830: {
831: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
832:
833: if (isset($aArgs['Query'])) {
834: $aArgs['Query']->leftJoin('adav_shared_addressbooks', 'adav_cards.addressbookid', '=', 'adav_shared_addressbooks.addressbook_id');
835: }
836: $query->orWhere(function ($q) use ($userPublicId, $aArgs) {
837: $q->where('adav_shared_addressbooks.principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId);
838: if (is_array($aArgs['UUID'])) {
839: $ids = $aArgs['UUID'];
840: if (count($aArgs['UUID']) === 0) {
841: $ids = [null];
842: }
843: $q->whereIn('adav_cards.id', $ids);
844: } else {
845: $q->where('adav_cards.id', $aArgs['UUID']);
846: }
847: });
848:
849: $addressbook = $this->GetSharedWithAllAddressbook($aArgs['UserId']);
850: $query->orWhere(function ($q) use ($addressbook, $aArgs) {
851: $q->where('adav_addressbooks.id', $addressbook['id']);
852: if (is_array($aArgs['UUID'])) {
853: if (count($aArgs['UUID']) > 0) {
854: $q->whereIn('adav_cards.id', $aArgs['UUID']);
855: }
856: } else {
857: $q->where('adav_cards.id', $aArgs['UUID']);
858: }
859: });
860: }
861:
862: public function onBeforeCreateContact(&$aArgs, &$mResult)
863: {
864: if (isset($aArgs['Contact'])) {
865: if (isset($aArgs['UserId'])) {
866: $aArgs['Contact']['UserId'] = $aArgs['UserId'];
867: }
868: $this->populateContactArguments($aArgs['Contact'], $mResult);
869: }
870: }
871:
872: /**
873: *
874: */
875: public function populateContactArguments(&$aArgs, &$mResult)
876: {
877: if (isset($aArgs['Storage'], $aArgs['UserId'])) {
878: $aStorageParts = \explode('-', $aArgs['Storage']);
879: if (count($aStorageParts) > 1) {
880: $iAddressBookId = $aStorageParts[1];
881: if ($aStorageParts[0] === StorageType::Shared) {
882: if (!is_numeric($iAddressBookId)) {
883: return;
884: }
885: $aArgs['Storage'] = $aStorageParts[0];
886: $aArgs['AddressBookId'] = $iAddressBookId;
887:
888: $mResult = true;
889: }
890: } else {
891: if ($aStorageParts[0] === StorageType::Shared) {
892: $abook = $this->GetSharedWithAllAddressbook($aArgs['UserId']);
893: if ($abook) {
894: $aArgs['AddressBookId'] = $abook['id'];
895:
896: $mResult = true;
897: }
898: }
899: }
900: }
901: }
902:
903: public function onBeforeDeleteContacts(&$aArgs, &$mResult)
904: {
905: $this->populateContactArguments($aArgs, $mResult);
906: if (isset($aArgs['AddressBookId'])) {
907: $aArgs['Storage'] = $aArgs['AddressBookId'];
908: }
909: }
910:
911: public function onAfterCheckAccessToAddressBook(&$aArgs, &$mResult)
912: {
913: if (isset($aArgs['User'], $aArgs['AddressBookId'])) {
914: $query = Capsule::connection()->table('adav_addressbooks')
915: ->select('adav_addressbooks.id', 'adav_shared_addressbooks.access', 'adav_shared_addressbooks.group_id')
916: ->leftJoin('adav_shared_addressbooks', 'adav_addressbooks.id', '=', 'adav_shared_addressbooks.addressbook_id')
917: ->where('adav_shared_addressbooks.principaluri', Constants::PRINCIPALS_PREFIX . $aArgs['User']->PublicId)
918: ->where('adav_addressbooks.id', $aArgs['AddressBookId']);
919: $abooks = $query->get();
920:
921: $val = null;
922: foreach ($abooks as $abook) {
923: if ($val) {
924: $iAccess = $val['Access'];
925: if ($val['GroupId'] === 0 && $iAccess === Access::NoAccess) {
926: continue;
927: }
928: $iNewAccess = $abook->access;
929: if ((int) $abook->group_id === 0) { //personal sharing
930: $val['Access'] = $iNewAccess;
931: } else { // group sharing
932: if ($iNewAccess !== Access::Read) {
933: if ($iAccess < $iNewAccess || $iNewAccess === Access::NoAccess) {
934: $val['Access'] = $iNewAccess;
935: }
936: } elseif ($iAccess !== Access::Write) {
937: $val['Access'] = $iNewAccess;
938: }
939: }
940: }
941:
942: $val = [
943: 'Access' => (int) $abook->access,
944: 'GroupId' => (int) $abook->group_id
945: ];
946: }
947: if ($val) {
948: $access = $val['Access'];
949: $mResult = ($access === Access::Write || $access === $aArgs['Access']);
950:
951: return true;
952: } else {
953: if (isset($aArgs['User'], $aArgs['AddressBookId'])) {
954: $addressbook = $this->GetSharedWithAllAddressbook($aArgs['User']->Id);
955: if ($addressbook && $addressbook['id'] == $aArgs['AddressBookId']) {
956: $mResult = true;
957:
958: return $mResult;
959: }
960: }
961: }
962: };
963: }
964:
965: /**
966: *
967: */
968: public function onBeforeUpdateAddressbookShare(&$aArgs)
969: {
970: if (isset($aArgs['Id'], $aArgs['UserId'])) {
971: $aStorageParts = \explode('-', $aArgs['Id']);
972: if (count($aStorageParts) > 1) {
973: $iAddressBookId = $aStorageParts[1];
974:
975: if (!is_numeric($iAddressBookId)) {
976: return;
977: }
978: $aArgs['Id'] = $iAddressBookId;
979: } elseif (isset($aStorageParts[0])) {
980: $storagesMapToAddressbooks = ContactsModule::Decorator()->GetStoragesMapToAddressbooks();
981: if (isset($storagesMapToAddressbooks[$aStorageParts[0]])) {
982: $addressbookUri = $storagesMapToAddressbooks[$aStorageParts[0]];
983: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
984: if ($userPublicId) {
985: $row = Capsule::connection()->table('adav_addressbooks')
986: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
987: ->where('uri', $addressbookUri)
988: ->select('adav_addressbooks.id as addressbook_id')->first();
989: if ($row) {
990: $aArgs['Id'] = $row->addressbook_id;
991: }
992: }
993: }
994: }
995: }
996: }
997:
998: public function onAfterGetStoragesMapToAddressbooks(&$aArgs, &$mResult)
999: {
1000: $mResult = array_merge($mResult, $this->storagesMapToAddressbooks);
1001: }
1002: }
1003: