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: CASE
358: WHEN ' . Capsule::connection()->getTablePrefix() . 'adav_cards.addressbookid IN (' . implode(',', $ids) . ') THEN true
359: ELSE false
360: END as Shared'
361: ));
362: } else {
363: $aArgs['Query']->addSelect(Capsule::connection()->raw(
364: 'false as Shared'
365: ));
366: }
367: }
368: }
369: }
370:
371: public function onAfterUpdateSharedContacts($aArgs, &$mResult)
372: {
373: $oContacts = ContactsModule::Decorator();
374: $aUUIDs = isset($aArgs['UUIDs']) ? $aArgs['UUIDs'] : [];
375:
376: foreach ($aUUIDs as $sUUID) {
377: $oContact = $oContacts->GetContact($sUUID, $aArgs['UserId']);
378: if ($oContact instanceof Contact) {
379: $FromStorage = $oContact->Storage;
380: $ToStorage = $FromStorage;
381: if ($oContact->Storage === StorageType::Shared) {
382: $ToStorage = StorageType::Personal;
383: } elseif ($oContact->Storage === StorageType::Personal) {
384: $ToStorage = StorageType::Shared;
385: }
386: $oContacts->MoveContactsToStorage($aArgs['UserId'], $FromStorage, $ToStorage, [$sUUID]);
387: }
388: }
389: }
390:
391: public function onAfterCheckAccessToObject(&$aArgs, &$mResult)
392: {
393: $oUser = $aArgs['User'];
394: $oContact = isset($aArgs['Contact']) ? $aArgs['Contact'] : null;
395: $Access = isset($aArgs['Access']) ? (int) $aArgs['Access'] : null;
396:
397: if ($oContact instanceof Contact) {
398: if ($oContact->IdUser === $oUser->Id) {
399: $mResult = true;
400: return true; // break other subscriptions
401: }
402: if ($oContact->Storage === StorageType::Shared) {
403: if ($oUser->Role !== UserRole::SuperAdmin && $oUser->IdTenant !== $oContact->IdTenant) {
404: $mResult = false;
405: } else {
406: $mResult = true;
407: }
408: } elseif ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
409: $dBPrefix = Api::GetSettings()->DBPrefix;
410: $sql = "select ab.*, sab.access, ab.id as addressbook_id, cu.Id as UserId from " . $dBPrefix . "adav_shared_addressbooks sab
411: left join " . $dBPrefix . "adav_addressbooks ab on sab.addressbook_id = ab.id
412: left join " . $dBPrefix . "core_users cu on ab.principaluri = CONCAT('principals/', cu.PublicId)
413: where sab.principaluri = ? and cu.Id = ? and ab.id = ?";
414:
415: $stmt = Api::GetPDO()->prepare($sql);
416:
417: $stmt->execute([
418: Constants::PRINCIPALS_PREFIX . $oUser->PublicId,
419: $oContact->IdUser,
420: $oContact->AddressBookId
421: ]);
422:
423: $abook = $stmt->fetch(\PDO::FETCH_ASSOC);
424: if ($abook) {
425: if ((int) $abook['access'] === Access::NoAccess) {
426: $mResult = false;
427: } elseif (isset($Access)) {
428: if ($Access === (int) $abook['access'] && $Access === Access::Write) {
429: $mResult = true;
430: } else {
431: $mResult = false;
432: }
433: } else {
434: $mResult = true;
435: }
436:
437: return true; // break other subscriptions
438: }
439: }
440: }
441: }
442:
443: public function onGetContactSuggestions(&$aArgs, &$mResult)
444: {
445: if ($aArgs['Storage'] === 'all' || $aArgs['Storage'] === StorageType::Shared) {
446: $mResult[StorageType::Shared] = ContactsModule::Decorator()->GetContacts(
447: $aArgs['UserId'],
448: StorageType::Shared,
449: 0,
450: $aArgs['Limit'],
451: $aArgs['SortField'],
452: $aArgs['SortOrder'],
453: $aArgs['Search']
454: );
455: }
456: }
457:
458: /**
459: * The methods corrects the storage type of contacts related to the shared addressbooks.
460: */
461: public function onGetContacts(&$aArgs, &$mResult)
462: {
463: if ($aArgs['Storage'] === 'all' || $aArgs['Storage'] === StorageType::Shared) {
464: $aSharedBooks = $this->GetAddressbooks($aArgs['UserId']);
465: foreach ($mResult['List'] as &$aContact) {
466: $aStorageParts = explode('-', $aContact['Storage']);
467: // only personal and custom addressbooks can be shared
468: if (in_array($aStorageParts[0], [ StorageType::Personal,StorageType::AddressBook ])) {
469: foreach ($aSharedBooks as $aBook) {
470: // check if contact's addressbook exist in list of shared with user addressbooks
471: if ($aBook['EntityId'] === $aContact['AddressBookId']) {
472: $aContact['Storage'] = StorageType::Shared . '-' . $aContact['AddressBookId'];
473: }
474: }
475: }
476: }
477: }
478: }
479:
480: /**
481: * The methods corrects the storage type of contacts related to the shared addressbooks.
482: */
483: public function onGetContactsByUids(&$aArgs, &$mResult)
484: {
485: if (is_array($mResult)) {
486: $aSharedBooks = $this->GetAddressbooks($aArgs['UserId']);
487: foreach ($mResult as &$oContact) {
488: $aStorageParts = explode('-', $oContact->Storage);
489: // only personal and custom addressbooks can be shared
490: if (in_array($aStorageParts[0], [ StorageType::Personal, StorageType::AddressBook ])) {
491: foreach ($aSharedBooks as $aBook) {
492: // check if contact's addressbook exist in list of shared with user addressbooks
493: if ($aBook['EntityId'] === $oContact->AddressBookId) {
494: $oContact->Storage = StorageType::Shared . '-' . $oContact->AddressBookId;
495: }
496: }
497: }
498: }
499: }
500: }
501:
502: public function GetSharedWithAllAddressbook($UserId)
503: {
504: Api::CheckAccess($UserId);
505:
506: $addressbook = false;
507:
508: $oUser = Api::getUserById($UserId);
509: if ($oUser) {
510: $sPrincipalUri = Constants::PRINCIPALS_PREFIX . $oUser->IdTenant . '_' . Constants::DAV_TENANT_PRINCIPAL;
511: $addressbook = Backend::Carddav()->getAddressBookForUser($sPrincipalUri, Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME);
512: if (!$addressbook) {
513: if (Backend::Carddav()->createAddressBook($sPrincipalUri, Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME, ['{DAV:}displayname' => Constants::ADDRESSBOOK_SHARED_WITH_ALL_DISPLAY_NAME])) {
514: $addressbook = Backend::Carddav()->getAddressBookForUser($sPrincipalUri, Constants::ADDRESSBOOK_SHARED_WITH_ALL_NAME);
515: }
516: }
517: }
518:
519: return $addressbook;
520: }
521:
522: public function onAfterGetAddressBooks(&$aArgs, &$mResult)
523: {
524: if (!is_array($mResult)) {
525: $mResult = [];
526: }
527: foreach ($mResult as $key => $abook) {
528: $mResult[$key]['Shares'] = self::Decorator()->GetSharesForAddressbook($aArgs['UserId'], $abook['Id']);
529: }
530: $mResult = array_merge(
531: $mResult,
532: $this->GetAddressbooks($aArgs['UserId'])
533: );
534:
535: $addressbook = $this->GetSharedWithAllAddressbook($aArgs['UserId']);
536: if ($addressbook) {
537: /**
538: * @var array $addressbook
539: */
540: $mResult[] = [
541: 'Id' => 'shared',
542: 'EntityId' => (int) $addressbook['id'],
543: 'CTag' => (int) $addressbook['{http://sabredav.org/ns}sync-token'],
544: 'Display' => true,
545: 'Order' => 1,
546: 'DisplayName' => $addressbook['{DAV:}displayname'],
547: 'Uri' => $addressbook['uri'],
548: 'Url' => 'addressbooks/' . $addressbook['uri'],
549: ];
550: }
551: }
552:
553: // @todo: it doesn't work because addressbook id never has 3 parts
554: // public function onPopulateContactModel(&$oContact, &$mResult)
555: // {
556: // if ($oContact instanceof Contact) {
557: // $aStorageParts = \explode('-', $oContact->Storage);
558: // if (is_array($aStorageParts) && count($aStorageParts) === 3 && $aStorageParts[0] === StorageType::Shared) {
559: // $abooks = $this->GetAddressbooks($oContact->IdUser);
560: // foreach ($abooks as $abook) {
561: // if ($abook['Id'] === $oContact->Storage) {
562: // if ($aStorageParts[2] === StorageType::Personal) {
563: // $oContact->Storage = StorageType::Personal;
564: // } else {
565: // $oContact->Storage = StorageType::AddressBook;
566: // $oContact->AddressBookId = (int) $aStorageParts[2];
567: // }
568:
569: // $oContact->IdUser = (int) $aStorageParts[1];
570: // break;
571: // }
572: // }
573: // }
574: // }
575: // }
576:
577: public function UpdateAddressbookShare($UserId, $Id, $Shares)
578: {
579: $mResult = true;
580:
581: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
582: Api::CheckAccess($UserId);
583:
584: if (!isset($Id) || !is_array($Shares)) {
585: throw new InvalidArgumentException("", Notifications::InvalidInputParameter);
586: }
587:
588: try {
589: $oUser = Api::getUserById($UserId);
590: $currentABookShares = $this->_getSharesForAddressbook($UserId, $Id);
591:
592: $newABookShares = [];
593: foreach ($Shares as $share) {
594: if (isset($share['GroupId'])) {
595: $aUsers = CoreModule::Decorator()->GetGroupUsers($oUser->IdTenant, (int) $share['GroupId']);
596: foreach ($aUsers as $aUser) {
597: $newABookShares[] = [
598: 'PublicId' => $aUser['PublicId'],
599: 'Access' => (int) $share['Access'],
600: 'GroupId' => (int) $share['GroupId'],
601: ];
602: }
603: } else {
604: $share['GroupId'] = 0;
605: $newABookShares[] = $share;
606: }
607: }
608:
609: $currentShares = array_map(function ($share) {
610: return \json_encode([
611: basename($share['principaluri']),
612: $share['group_id']
613: ]);
614: }, $currentABookShares);
615:
616: $newShares = array_map(function ($share) {
617: return \json_encode([
618: $share['PublicId'],
619: $share['GroupId']
620: ]);
621: }, $newABookShares);
622:
623: $sharesToDelete = array_diff($currentShares, $newShares);
624: $sharesToCreate = array_diff($newShares, $currentShares);
625: $sharesToUpdate = array_intersect($currentShares, $newShares);
626:
627: if (count($sharesToDelete) > 0) {
628: $this->deleteShareByPublicIds($UserId, $Id, $sharesToDelete);
629: }
630:
631: foreach ($newABookShares as $share) {
632: $sharePublicIdAndGroupId = \json_encode([
633: $share['PublicId'],
634: $share['GroupId']
635: ]);
636: if (in_array($sharePublicIdAndGroupId, $sharesToCreate)) {
637: $this->createShare($UserId, $Id, $share);
638: }
639: if (in_array($sharePublicIdAndGroupId, $sharesToUpdate)) {
640: $this->updateShare($UserId, $Id, $share);
641: }
642: }
643:
644: $mResult = true;
645: } catch (\Exception $oException) {
646: Api::LogException($oException);
647: $mResult = false;
648: }
649:
650: return $mResult;
651: }
652:
653: public function LeaveShare($UserId, $Id)
654: {
655: Api::checkUserRoleIsAtLeast(UserRole::NormalUser);
656: Api::CheckAccess($UserId);
657:
658: $userPublicId = Api::getUserPublicIdById($UserId);
659:
660: $sharedAddressBook = Capsule::connection()->table('adav_shared_addressbooks')
661: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
662: ->where('addressbook_id', $Id)
663: ->where('group_id', 0)
664: ->first();
665:
666: if ($sharedAddressBook) {
667: Capsule::connection()->table('adav_shared_addressbooks')
668: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
669: ->where('addressbook_id', $Id)
670: ->where('group_id', 0)
671: ->update(['access' => Access::NoAccess]);
672: } else {
673: $stmt = Api::GetPDO()->prepare("insert into " . Api::GetSettings()->DBPrefix . "adav_shared_addressbooks
674: (principaluri, access, addressbook_id, addressbookuri, group_id)
675: values (?, ?, ?, ?, ?)");
676: $stmt->execute([Constants::PRINCIPALS_PREFIX . $userPublicId, Access::NoAccess, $Id, UUIDUtil::getUUID(), 0]);
677: }
678:
679: return true;
680: }
681:
682: public function onAfterDeleteGroup($aArgs, &$mResult)
683: {
684: if ($mResult) {
685: $dBPrefix = Api::GetSettings()->DBPrefix;
686: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where group_id = ?");
687: $stmt->execute([$aArgs['GroupId']]);
688: }
689: }
690:
691: /**
692: * @ignore
693: * @param array $aArgs Arguments of event.
694: * @param mixed $mResult Is passed by reference.
695: */
696: public function onBeforeDeleteUser($aArgs, &$mResult)
697: {
698: if (isset($aArgs['UserId'])) {
699: $this->oBeforeDeleteUser = Api::getUserById($aArgs['UserId']);
700: }
701: }
702:
703: /**
704: * @ignore
705: * @param array $aArgs Arguments of event.
706: * @param mixed $mResult Is passed by reference.
707: */
708: public function onAfterDeleteUser($aArgs, $mResult)
709: {
710: if ($mResult && $this->oBeforeDeleteUser instanceof User) {
711: $dBPrefix = Api::GetSettings()->DBPrefix;
712: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where principaluri = ?");
713: $stmt->execute([Constants::PRINCIPALS_PREFIX . $this->oBeforeDeleteUser->PublicId]);
714: }
715: }
716:
717: public function onAfterAddUsersToGroup($aArgs, &$mResult)
718: {
719: if ($mResult) {
720: foreach ($aArgs['UserIds'] as $iUserId) {
721: $userPublicId = Api::getUserPublicIdById($iUserId);
722: $sUserPrincipalUri = Constants::PRINCIPALS_PREFIX . $userPublicId;
723:
724: $dBPrefix = Api::GetSettings()->DBPrefix;
725: $stmt = Api::GetPDO()->prepare("select distinct addressbook_id, access from " . $dBPrefix . "adav_shared_addressbooks where group_id = ?");
726: $stmt->execute([$aArgs['GroupId']]);
727: $shares = $stmt->fetchAll(\PDO::FETCH_ASSOC);
728: foreach ($shares as $share) {
729: if (is_array($share)) {
730: $stmt = Api::GetPDO()->prepare("insert into " . $dBPrefix . "adav_shared_addressbooks
731: (principaluri, access, addressbook_id, addressbookuri, group_id)
732: values (?, ?, ?, ?, ?)");
733: $stmt->execute([$sUserPrincipalUri, $share['access'], $share['addressbook_id'], UUIDUtil::getUUID(), $aArgs['GroupId']]);
734: }
735: }
736: }
737: }
738: }
739:
740: public function onAfterCreateUser($aArgs, &$mResult)
741: {
742: if ($mResult) {
743: $oUser = User::find($mResult);
744: if ($oUser) {
745: $oGroup = CoreModule::getInstance()->GetAllGroup($oUser->IdTenant);
746: if ($oGroup) {
747: $newArgs = [
748: 'GroupId' => $oGroup->Id,
749: 'UserIds' => [$mResult]
750: ];
751: $newResult = true;
752: $this->onAfterAddUsersToGroup($newArgs, $newResult);
753: }
754: }
755: }
756: }
757:
758: public function onAfterUpdateUser($aArgs, &$mResult)
759: {
760: if ($mResult) {
761: $groupIds = $aArgs['GroupIds'] ?? null;
762: $userId = $aArgs['UserId'];
763:
764: if ($groupIds !== null) {
765: $userPublicId = Api::getUserPublicIdById($userId);
766: $sUserPrincipalUri = Constants::PRINCIPALS_PREFIX . $userPublicId;
767:
768: $dBPrefix = Api::GetSettings()->DBPrefix;
769: $stmt = Api::GetPDO()->prepare("select * from " . $dBPrefix . "adav_shared_addressbooks where group_id <> 0 and principaluri = ?");
770: $stmt->execute([$sUserPrincipalUri]);
771: $shares = $stmt->fetchAll(\PDO::FETCH_ASSOC);
772:
773: $currentGroupsIds = [];
774: if (is_array($shares)) {
775: $currentGroupsIds = array_map(function ($share) {
776: return $share['group_id'];
777: }, $shares);
778: }
779:
780: $groupsIdsToDelete = array_diff($currentGroupsIds, $groupIds);
781: $groupsIdsToCreate = array_diff($groupIds, $currentGroupsIds);
782:
783: if (count($groupsIdsToDelete) > 0) {
784: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks
785: where group_id in (" . \implode(',', $groupsIdsToDelete) . ") and principaluri = ?");
786: $stmt->execute([$sUserPrincipalUri]);
787: }
788:
789: if (count($groupsIdsToCreate) > 0) {
790: $stmt = Api::GetPDO()->prepare("select distinct addressbook_id, access, group_id from " . $dBPrefix . "adav_shared_addressbooks where group_id in (" . \implode(',', $groupsIdsToCreate) . ")");
791: $stmt->execute();
792: $shares = $stmt->fetchAll(\PDO::FETCH_ASSOC);
793: foreach ($shares as $share) {
794: if (is_array($share)) {
795: $stmt = Api::GetPDO()->prepare("insert into " . $dBPrefix . "adav_shared_addressbooks
796: (principaluri, access, addressbook_id, addressbookuri, group_id)
797: values (?, ?, ?, ?, ?)");
798: $stmt->execute([$sUserPrincipalUri, $share['access'], $share['addressbook_id'], UUIDUtil::getUUID(), $share['group_id']]);
799: }
800: }
801: }
802: }
803: }
804: }
805:
806: public function onAfterRemoveUsersFromGroup($aArgs, &$mResult)
807: {
808: if ($mResult) {
809: $principals = [];
810: foreach ($aArgs['UserIds'] as $iUserId) {
811: $oUser = Api::getUserById($iUserId);
812: $principals[] = Constants::PRINCIPALS_PREFIX . $oUser->PublicId;
813: }
814:
815: if (count($principals) > 0) {
816: $dBPrefix = Api::GetSettings()->DBPrefix;
817: $principals = array_map(function ($principal) {
818: return \Illuminate\Support\Str::padBoth($principal, strlen($principal) + 2, "'");
819: }, $principals);
820: $stmt = Api::GetPDO()->prepare("delete from " . $dBPrefix . "adav_shared_addressbooks where principaluri in (" . \implode(',', $principals) . ") and group_id = ?");
821: $stmt->execute([$aArgs['GroupId']]);
822: }
823: }
824: }
825:
826: public function onContactQueryBuilder(&$aArgs, &$query)
827: {
828: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
829:
830: if (isset($aArgs['Query'])) {
831: $aArgs['Query']->leftJoin('adav_shared_addressbooks', 'adav_cards.addressbookid', '=', 'adav_shared_addressbooks.addressbook_id');
832: }
833: $query->orWhere(function ($q) use ($userPublicId, $aArgs) {
834: $q->where('adav_shared_addressbooks.principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId);
835: if (is_array($aArgs['UUID'])) {
836: $ids = $aArgs['UUID'];
837: if (count($aArgs['UUID']) === 0) {
838: $ids = [null];
839: }
840: $q->whereIn('adav_cards.id', $ids);
841: } else {
842: $q->where('adav_cards.id', $aArgs['UUID']);
843: }
844: });
845:
846: $addressbook = $this->GetSharedWithAllAddressbook($aArgs['UserId']);
847: $query->orWhere(function ($q) use ($addressbook, $aArgs) {
848: $q->where('adav_addressbooks.id', $addressbook['id']);
849: if (is_array($aArgs['UUID'])) {
850: if (count($aArgs['UUID']) > 0) {
851: $q->whereIn('adav_cards.id', $aArgs['UUID']);
852: }
853: } else {
854: $q->where('adav_cards.id', $aArgs['UUID']);
855: }
856: });
857: }
858:
859: public function onBeforeCreateContact(&$aArgs, &$mResult)
860: {
861: if (isset($aArgs['Contact'])) {
862: if (isset($aArgs['UserId'])) {
863: $aArgs['Contact']['UserId'] = $aArgs['UserId'];
864: }
865: $this->populateContactArguments($aArgs['Contact'], $mResult);
866: }
867: }
868:
869: /**
870: *
871: */
872: public function populateContactArguments(&$aArgs, &$mResult)
873: {
874: if (isset($aArgs['Storage'], $aArgs['UserId'])) {
875: $aStorageParts = \explode('-', $aArgs['Storage']);
876: if (count($aStorageParts) > 1) {
877: $iAddressBookId = $aStorageParts[1];
878: if ($aStorageParts[0] === StorageType::Shared) {
879: if (!is_numeric($iAddressBookId)) {
880: return;
881: }
882: $aArgs['Storage'] = $aStorageParts[0];
883: $aArgs['AddressBookId'] = $iAddressBookId;
884:
885: $mResult = true;
886: }
887: } else {
888: if ($aStorageParts[0] === StorageType::Shared) {
889: $abook = $this->GetSharedWithAllAddressbook($aArgs['UserId']);
890: if ($abook) {
891: $aArgs['AddressBookId'] = $abook['id'];
892:
893: $mResult = true;
894: }
895: }
896: }
897: }
898: }
899:
900: public function onBeforeDeleteContacts(&$aArgs, &$mResult)
901: {
902: $this->populateContactArguments($aArgs, $mResult);
903: if (isset($aArgs['AddressBookId'])) {
904: $aArgs['Storage'] = $aArgs['AddressBookId'];
905: }
906: }
907:
908: public function onAfterCheckAccessToAddressBook(&$aArgs, &$mResult)
909: {
910: if (isset($aArgs['User'], $aArgs['AddressBookId'])) {
911: $query = Capsule::connection()->table('adav_addressbooks')
912: ->select('adav_addressbooks.id', 'adav_shared_addressbooks.access', 'adav_shared_addressbooks.group_id')
913: ->leftJoin('adav_shared_addressbooks', 'adav_addressbooks.id', '=', 'adav_shared_addressbooks.addressbook_id')
914: ->where('adav_shared_addressbooks.principaluri', Constants::PRINCIPALS_PREFIX . $aArgs['User']->PublicId)
915: ->where('adav_addressbooks.id', $aArgs['AddressBookId']);
916: $abooks = $query->get();
917:
918: $val = null;
919: foreach ($abooks as $abook) {
920: if ($val) {
921: $iAccess = $val['Access'];
922: if ($val['GroupId'] === 0 && $iAccess === Access::NoAccess) {
923: continue;
924: }
925: $iNewAccess = $abook->access;
926: if ((int) $abook->group_id === 0) { //personal sharing
927: $val['Access'] = $iNewAccess;
928: } else { // group sharing
929: if ($iNewAccess !== Access::Read) {
930: if ($iAccess < $iNewAccess || $iNewAccess === Access::NoAccess) {
931: $val['Access'] = $iNewAccess;
932: }
933: } elseif ($iAccess !== Access::Write) {
934: $val['Access'] = $iNewAccess;
935: }
936: }
937: }
938:
939: $val = [
940: 'Access' => (int) $abook->access,
941: 'GroupId' => (int) $abook->group_id
942: ];
943: }
944: if ($val) {
945: $access = $val['Access'];
946: $mResult = ($access === Access::Write || $access === $aArgs['Access']);
947:
948: return true;
949: } else {
950: if (isset($aArgs['User'], $aArgs['AddressBookId'])) {
951: $addressbook = $this->GetSharedWithAllAddressbook($aArgs['User']->Id);
952: if ($addressbook && $addressbook['id'] == $aArgs['AddressBookId']) {
953: $mResult = true;
954:
955: return $mResult;
956: }
957: }
958: }
959: };
960: }
961:
962: /**
963: *
964: */
965: public function onBeforeUpdateAddressbookShare(&$aArgs)
966: {
967: if (isset($aArgs['Id'], $aArgs['UserId'])) {
968: $aStorageParts = \explode('-', $aArgs['Id']);
969: if (count($aStorageParts) > 1) {
970: $iAddressBookId = $aStorageParts[1];
971:
972: if (!is_numeric($iAddressBookId)) {
973: return;
974: }
975: $aArgs['Id'] = $iAddressBookId;
976: } elseif (isset($aStorageParts[0])) {
977: $storagesMapToAddressbooks = ContactsModule::Decorator()->GetStoragesMapToAddressbooks();
978: if (isset($storagesMapToAddressbooks[$aStorageParts[0]])) {
979: $addressbookUri = $storagesMapToAddressbooks[$aStorageParts[0]];
980: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
981: if ($userPublicId) {
982: $row = Capsule::connection()->table('adav_addressbooks')
983: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
984: ->where('uri', $addressbookUri)
985: ->select('adav_addressbooks.id as addressbook_id')->first();
986: if ($row) {
987: $aArgs['Id'] = $row->addressbook_id;
988: }
989: }
990: }
991: }
992: }
993: }
994:
995: public function onAfterGetStoragesMapToAddressbooks(&$aArgs, &$mResult)
996: {
997: $mResult = array_merge($mResult, $this->storagesMapToAddressbooks);
998: }
999: }
1000: