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\PersonalContacts;
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\Classes\Contact;
15: use Aurora\Modules\Contacts\Module as ContactsModule;
16: use Illuminate\Database\Capsule\Manager as Capsule;
17:
18: /**
19: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
20: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
21: * @copyright Copyright (c) 2023, Afterlogic Corp.
22: *
23: * @property Settings $oModuleSettings
24: *
25: * @package Modules
26: */
27: class Module extends \Aurora\System\Module\AbstractModule
28: {
29: public static $sStorage = StorageType::Personal;
30: protected static $iStorageOrder = 0;
31:
32: protected $storagesMapToAddressbooks = [
33: StorageType::Personal => Constants::ADDRESSBOOK_DEFAULT_NAME,
34: StorageType::Collected => Constants::ADDRESSBOOK_COLLECTED_NAME,
35: ];
36:
37: public function init()
38: {
39: $this->subscribeEvent('Core::CreateUser::after', array($this, 'onAfterCreateUser'));
40: $this->subscribeEvent('Contacts::CreateContact::before', array($this, 'onBeforeCreateContact'));
41: $this->subscribeEvent('Contacts::PrepareFiltersFromStorage', array($this, 'onPrepareFiltersFromStorage'));
42: $this->subscribeEvent('Mail::ExtendMessageData', array($this, 'onExtendMessageData'));
43: $this->subscribeEvent('Contacts::CheckAccessToObject::after', array($this, 'onAfterCheckAccessToObject'));
44: $this->subscribeEvent('Contacts::GetContactSuggestions', array($this, 'onGetContactSuggestions'));
45:
46: $this->subscribeEvent('Contacts::GetAddressBooks::after', array($this, 'onAfterGetAddressBooks'));
47: $this->subscribeEvent('Contacts::ContactQueryBuilder', array($this, 'onContactQueryBuilder'));
48: $this->subscribeEvent('Contacts::DeleteContacts::before', array($this, 'onBeforeDeleteContacts'));
49: $this->subscribeEvent('Contacts::CheckAccessToAddressBook::after', array($this, 'onAfterCheckAccessToAddressBook'), 90);
50: $this->subscribeEvent('Contacts::GetStoragesMapToAddressbooks::after', array($this, 'onAfterGetStoragesMapToAddressbooks'));
51: $this->subscribeEvent('Contacts::GetContacts::before', array($this, 'populateContactArguments'));
52: $this->subscribeEvent('Contacts::GetContactsByEmails::before', array($this, 'populateContactArguments'));
53: $this->subscribeEvent('Contacts::PopulateContactArguments', array($this, 'populateContactArguments'));
54: $this->subscribeEvent('Contacts::Export::before', array($this, 'populateContactArguments'));
55: }
56:
57: /**
58: * @return Module
59: */
60: public static function getInstance()
61: {
62: return parent::getInstance();
63: }
64:
65: /**
66: * @return Module
67: */
68: public static function Decorator()
69: {
70: return parent::Decorator();
71: }
72:
73: /**
74: * @return Settings
75: */
76: public function getModuleSettings()
77: {
78: return $this->oModuleSettings;
79: }
80:
81: public function onAfterCreateUser($aArgs, &$mResult)
82: {
83: if ($mResult) {
84: $principalUri = Constants::PRINCIPALS_PREFIX . $aArgs['PublicId'];
85:
86: Backend::Carddav()->createAddressBook(
87: $principalUri,
88: Constants::ADDRESSBOOK_DEFAULT_NAME,
89: [
90: '{DAV:}displayname' => Constants::ADDRESSBOOK_DEFAULT_DISPLAY_NAME
91: ]
92: );
93:
94: Backend::Carddav()->createAddressBook(
95: $principalUri,
96: Constants::ADDRESSBOOK_COLLECTED_NAME,
97: [
98: '{DAV:}displayname' => Constants::ADDRESSBOOK_COLLECTED_DISPLAY_NAME
99: ]
100: );
101: }
102: }
103:
104: public function onBeforeCreateContact(&$aArgs, &$mResult)
105: {
106: if (isset($aArgs['Contact'])) {
107: if (isset($aArgs['UserId'])) {
108: $aArgs['Contact']['UserId'] = $aArgs['UserId'];
109: }
110: $this->populateContactArguments($aArgs['Contact'], $mResult);
111: }
112: }
113:
114: public function onBeforeDeleteContacts(&$aArgs, &$mResult)
115: {
116: $this->populateContactArguments($aArgs, $mResult);
117: if (isset($aArgs['AddressBookId'])) {
118: $aArgs['Storage'] = $aArgs['AddressBookId'];
119: }
120: }
121:
122: public function onPrepareFiltersFromStorage(&$aArgs, &$mResult)
123: {
124: if (isset($aArgs['Storage'])) {
125: if ($aArgs['Storage'] === StorageType::All) {
126: $oUser = Api::getUserById($aArgs['UserId']);
127: if ($oUser) {
128: $aArgs['IsValid'] = true;
129: $ids = Capsule::connection()->table('adav_addressbooks')
130: ->select('id')
131: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $oUser->PublicId)
132: ->pluck('id')->toArray();
133: if ($ids) {
134: $mResult->orWhere(function ($q) use ($ids, $aArgs) {
135: $q->whereIn('adav_cards.addressbookid', $ids);
136: if ((isset($aArgs['Suggestions']) && !$aArgs['Suggestions']) || !isset($aArgs['Suggestions'])) {
137: $q->where('adav_addressbooks.uri', '!=', Constants::ADDRESSBOOK_COLLECTED_NAME);
138: }
139: });
140: }
141: }
142: } elseif (isset($aArgs['AddressBookId'])) {
143: $aArgs['IsValid'] = true;
144: $mResult->orWhere('adav_cards.addressbookid', (int) $aArgs['AddressBookId']);
145: }
146: if (isset($aArgs['Query'])) {
147: $aArgs['Query']->join('adav_addressbooks', 'adav_addressbooks.id', '=', 'adav_cards.addressbookid');
148: $aArgs['Query']->addSelect(Capsule::connection()->raw(
149: '
150: CASE
151: WHEN ' . Capsule::connection()->getTablePrefix() . 'adav_addressbooks.uri = \'' . Constants::ADDRESSBOOK_COLLECTED_NAME . '\' THEN true
152: ELSE false
153: END as Auto'
154: ));
155: }
156: }
157: }
158:
159: public function onExtendMessageData($aData, &$oMessage)
160: {
161: $oApiFileCache = new \Aurora\System\Managers\Filecache();
162:
163: $oUser = Api::getAuthenticatedUser();
164:
165: foreach ($aData as $aDataItem) {
166: $oPart = $aDataItem['Part'];
167: $bVcard = $oPart instanceof \MailSo\Imap\BodyStructure &&
168: ($oPart->ContentType() === 'text/vcard' || $oPart->ContentType() === 'text/x-vcard');
169: $sData = $aDataItem['Data'];
170: if ($bVcard && !empty($sData)) {
171: $oContact = new Contact();
172: try {
173: $oContact->InitFromVCardStr($oUser->Id, $sData);
174:
175: $oContact->UUID = '';
176:
177: $bContactExists = false;
178: if (0 < strlen($oContact->ViewEmail)) {
179: $aLocalContacts = ContactsModule::Decorator()->GetContactsByEmails(
180: $oUser->Id,
181: self::$sStorage,
182: [$oContact->ViewEmail]
183: );
184: $oLocalContact = count($aLocalContacts) > 0 ? $aLocalContacts[0] : null;
185: if ($oLocalContact) {
186: $oContact->UUID = $oLocalContact->UUID;
187: $bContactExists = true;
188: }
189: }
190:
191: $sTemptFile = md5($sData) . '.vcf';
192: if ($oApiFileCache && $oApiFileCache->put($oUser->UUID, $sTemptFile, $sData)) { // Temp files with access from another module should be stored in System folder
193: if (class_exists('\Aurora\Modules\Mail\Classes\Vcard')) {
194: $oVcard = \Aurora\Modules\Mail\Classes\Vcard::createInstance();
195:
196: $oVcard->Uid = $oContact->UUID;
197: $oVcard->File = $sTemptFile;
198: $oVcard->Exists = !!$bContactExists;
199: $oVcard->Name = $oContact->FullName;
200: $oVcard->Email = $oContact->ViewEmail;
201:
202: $oMessage->addExtend('VCARD', $oVcard);
203: }
204: } else {
205: Api::Log('Can\'t save temp file "' . $sTemptFile . '"', \Aurora\System\Enums\LogLevel::Error);
206: }
207: } catch(\Exception $oEx) {
208: Api::LogException($oEx);
209: }
210: }
211: }
212: }
213:
214: public function onAfterCheckAccessToObject(&$aArgs, &$mResult)
215: {
216: $oUser = $aArgs['User'];
217: $oContact = isset($aArgs['Contact']) ? $aArgs['Contact'] : null;
218:
219: if ($oContact instanceof Contact && $oContact->Storage === self::$sStorage) {
220: if ($oUser->Role !== \Aurora\System\Enums\UserRole::SuperAdmin && $oUser->Id !== $oContact->IdUser) {
221: $mResult = false;
222: } else {
223: $mResult = true;
224: }
225: }
226: }
227:
228: public function onGetContactSuggestions(&$aArgs, &$mResult)
229: {
230: if ($aArgs['Storage'] === 'all' || $aArgs['Storage'] === self::$sStorage) {
231: $mResult['personal'] = ContactsModule::Decorator()->GetContacts(
232: $aArgs['UserId'],
233: self::$sStorage,
234: 0,
235: $aArgs['Limit'],
236: $aArgs['SortField'],
237: $aArgs['SortOrder'],
238: $aArgs['Search']
239: );
240: }
241: }
242:
243: /**
244: *
245: */
246: public function populateContactArguments(&$aArgs, &$mResult)
247: {
248: if (isset($aArgs['Storage'], $aArgs['UserId'])) {
249: $aStorageParts = \explode('-', $aArgs['Storage']);
250: if (count($aStorageParts) > 1) {
251: $iAddressBookId = $aStorageParts[1];
252: if ($aStorageParts[0] === StorageType::AddressBook) {
253: if (!is_numeric($iAddressBookId)) {
254: return;
255: }
256: $aArgs['Storage'] = $aStorageParts[0];
257: $aArgs['AddressBookId'] = $iAddressBookId;
258:
259: $mResult = true;
260: }
261: } elseif (isset($aStorageParts[0])) {
262: if (isset($this->storagesMapToAddressbooks[$aStorageParts[0]])) {
263: $addressbookUri = $this->storagesMapToAddressbooks[$aStorageParts[0]];
264: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
265: if ($userPublicId) {
266: $row = Capsule::connection()->table('adav_addressbooks')
267: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
268: ->where('uri', $addressbookUri)
269: ->select('adav_addressbooks.id as addressbook_id')->first();
270: if ($row) {
271: $aArgs['AddressBookId'] = $row->addressbook_id;
272:
273: $mResult = true;
274: }
275: }
276: }
277: }
278: }
279: }
280:
281: /**
282: * @param array $addressBooks
283: * @param string $principalUri
284: * @return void
285: */
286: protected function createMissingAddressBooks(&$addressBooks, $principalUri)
287: {
288: $result = false;
289: if (!collect($addressBooks)->where('uri', Constants::ADDRESSBOOK_DEFAULT_NAME)->first()) {
290: $result = !!Backend::Carddav()->createAddressBook(
291: $principalUri,
292: Constants::ADDRESSBOOK_DEFAULT_NAME,
293: [
294: '{DAV:}displayname' => Constants::ADDRESSBOOK_DEFAULT_DISPLAY_NAME
295: ]
296: );
297: }
298:
299: if (!collect($addressBooks)->where('uri', Constants::ADDRESSBOOK_COLLECTED_NAME)->first()) {
300: $cardId = Backend::Carddav()->createAddressBook(
301: $principalUri,
302: Constants::ADDRESSBOOK_COLLECTED_NAME,
303: [
304: '{DAV:}displayname' => Constants::ADDRESSBOOK_COLLECTED_DISPLAY_NAME
305: ]
306: );
307: if (!$result) {
308: $result = !!$cardId;
309: }
310: }
311:
312: if ($result) {
313: $addressBooks = Backend::Carddav()->getAddressBooksForUser($principalUri);
314: }
315: }
316:
317: /**
318: *
319: */
320: public function onAfterGetAddressBooks(&$aArgs, &$mResult)
321: {
322: if (!is_array($mResult)) {
323: $mResult = [];
324: }
325:
326: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
327: $principalUri = Constants::PRINCIPALS_PREFIX . $userPublicId;
328: $aAddressBooks = Backend::Carddav()->getAddressBooksForUser($principalUri);
329:
330: $this->createMissingAddressBooks($aAddressBooks, $principalUri);
331:
332: foreach ($aAddressBooks as $oAddressBook) {
333: $storage = array_search($oAddressBook['uri'], $this->storagesMapToAddressbooks);
334: /**
335: * @var array $oAddressBook
336: */
337: $mResult[] = [
338: 'Id' => $storage ? $storage : StorageType::AddressBook . '-' . $oAddressBook['id'],
339: 'EntityId' => (int) $oAddressBook['id'],
340: 'CTag' => (int) $oAddressBook['{http://sabredav.org/ns}sync-token'],
341: 'Display' => $oAddressBook['uri'] !== Constants::ADDRESSBOOK_COLLECTED_NAME,
342: 'Owner' => basename($oAddressBook['principaluri']),
343: 'Order' => 1,
344: 'DisplayName' => $oAddressBook['{DAV:}displayname'],
345: 'Uri' => $oAddressBook['uri'],
346: 'Url' => 'addressbooks/' . $oAddressBook['uri'],
347: ];
348: }
349: }
350:
351: public function onContactQueryBuilder(&$aArgs, &$query)
352: {
353: $userPublicId = Api::getUserPublicIdById($aArgs['UserId']);
354: $query->orWhere(function ($q) use ($userPublicId, $aArgs) {
355: $q->where('adav_addressbooks.principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId);
356: if (is_array($aArgs['UUID'])) {
357: $ids = $aArgs['UUID'];
358: if (count($aArgs['UUID']) === 0) {
359: $ids = [null];
360: }
361: $q->whereIn('adav_cards.id', $ids);
362: } else {
363: $q->where('adav_cards.id', $aArgs['UUID']);
364: }
365: });
366: }
367:
368: public function onAfterCheckAccessToAddressBook(&$aArgs, &$mResult)
369: {
370: if (isset($aArgs['User'], $aArgs['AddressBookId'])) {
371: $mResult = !!Capsule::connection()->table('adav_addressbooks')
372: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $aArgs['User']->PublicId)
373: ->where('id', $aArgs['AddressBookId'])
374: ->first();
375: if ($mResult) {
376: return true;
377: }
378: }
379: }
380:
381: public function onAfterGetStoragesMapToAddressbooks(&$aArgs, &$mResult)
382: {
383: $mResult = array_merge($mResult, $this->storagesMapToAddressbooks);
384: }
385: }
386: