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\Contacts;
9:
10: use Afterlogic\DAV\Backend;
11: use Afterlogic\DAV\Constants;
12: use Aurora\Api;
13: use Aurora\Modules\Contacts\Enums\Access;
14: use Aurora\Modules\Contacts\Enums\StorageType;
15: use Aurora\Modules\Contacts\Enums\SortField;
16: use Aurora\System\Enums\SortOrder;
17: use Aurora\Modules\Contacts\Classes\Contact;
18: use Aurora\Modules\Contacts\Classes\VCard\Helper;
19: use Aurora\Modules\Contacts\Models\ContactCard;
20: use Aurora\Modules\Contacts\Classes\Group;
21: use Aurora\Modules\Core\Module as CoreModule;
22: use Aurora\System\Exceptions\ApiException;
23: use Aurora\System\Notifications;
24: use Illuminate\Database\Eloquent\Builder;
25: use Illuminate\Database\Capsule\Manager as Capsule;
26: use Sabre\DAV\UUIDUtil;
27: use Sabre\DAV\PropPatch;
28:
29: /**
30: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
31: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
32: * @copyright Copyright (c) 2023, Afterlogic Corp.
33: *
34: * @property Settings $oModuleSettings
35: *
36: * @package Modules
37: */
38: class Module extends \Aurora\System\Module\AbstractModule
39: {
40: protected $aImportExportFormats = ['csv', 'vcf'];
41:
42: protected $userPublicIdToDelete = null;
43:
44: /**
45: * @return Module
46: */
47: public static function getInstance()
48: {
49: return parent::getInstance();
50: }
51:
52: /**
53: * @return Module
54: */
55: public static function Decorator()
56: {
57: return parent::Decorator();
58: }
59:
60: /**
61: * @return Settings
62: */
63: public function getModuleSettings()
64: {
65: return $this->oModuleSettings;
66: }
67:
68: /**
69: * Initializes Contacts Module.
70: *
71: * @ignore
72: */
73: public function init()
74: {
75: $this->subscribeEvent('Mail::AfterUseEmails', array($this, 'onAfterUseEmails'));
76: $this->subscribeEvent('Mail::GetBodyStructureParts', array($this, 'onGetBodyStructureParts'));
77: $this->subscribeEvent('Core::DeleteUser::before', array($this, 'onBeforeDeleteUser'));
78: $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser'));
79:
80: $this->subscribeEvent('System::toResponseArray::after', array($this, 'onContactToResponseArray'));
81:
82: $this->denyMethodsCallByWebApi([
83: 'UpdateContactObject',
84: 'CheckAccessToAddressBook',
85: 'CheckAccessToObject'
86: ]);
87: }
88:
89: /***** public functions might be called with web API *****/
90: /**
91: * @apiDefine Contacts Contacts Module
92: * Main Contacts module. It provides PHP and Web APIs for managing contacts.
93: */
94:
95: /**
96: * @api {post} ?/Api/ GetSettings
97: * @apiName GetSettings
98: * @apiGroup Contacts
99: * @apiDescription Obtains list of module settings for authenticated user.
100: *
101: * @apiHeader {string} [Authorization] "Bearer " + Authentication token which was received as the result of Core.Login method.
102: * @apiHeaderExample {json} Header-Example:
103: * {
104: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
105: * }
106: *
107: * @apiParam {string=Contacts} Module Module name
108: * @apiParam {string=GetSettings} Method Method name
109: *
110: * @apiParamExample {json} Request-Example:
111: * {
112: * Module: 'Contacts',
113: * Method: 'GetSettings'
114: * }
115: *
116: * @apiSuccess {object[]} Result Array of response objects.
117: * @apiSuccess {string} Result.Module Module name.
118: * @apiSuccess {string} Result.Method Method name.
119: * @apiSuccess {mixed} Result.Result List of module settings in case of success, otherwise **false**.
120: * @apiSuccess {int} Result.Result.ContactsPerPage=20 Count of contacts that will be displayed on one page.
121: * @apiSuccess {string} Result.Result.ImportContactsLink=&quot;&quot; Link for learning more about CSV format.
122: * @apiSuccess {array} Result.Result.Storages='[]' List of storages wich will be shown in the interface.
123: * @apiSuccess {array} Result.Result.ImportExportFormats='[]' List of formats that can be used for import and export contacts.
124: * @apiSuccess {array} Result.Result.\Aurora\Modules\Contacts\Enums\PrimaryEmail='[]' Enumeration with primary email values.
125: * @apiSuccess {array} Result.Result.\Aurora\Modules\Contacts\Enums\PrimaryPhone='[]' Enumeration with primary phone values.
126: * @apiSuccess {array} Result.Result.\Aurora\Modules\Contacts\Enums\PrimaryAddress='[]' Enumeration with primary address values.
127: * @apiSuccess {array} Result.Result.\Aurora\Modules\Contacts\Enums\SortField='[]' Enumeration with sort field values.
128: * @apiSuccess {int} [Result.ErrorCode] Error code
129: *
130: * @apiSuccessExample {json} Success response example:
131: * {
132: * Module: 'Contacts',
133: * Method: 'GetSettings',
134: * Result: { ContactsPerPage: 20, ImportContactsLink: '', Storages: ['personal', 'team'],
135: * ImportExportFormats: ['csv', 'vcf'], \Aurora\Modules\Contacts\Enums\PrimaryEmail: {'Personal': 0, 'Business': 1, 'Other': 2},
136: * \Aurora\Modules\Contacts\Enums\PrimaryPhone: {'Mobile': 0, 'Personal': 1, 'Business': 2},
137: * \Aurora\Modules\Contacts\Enums\PrimaryAddress: {'Personal': 0, 'Business': 1},
138: * \Aurora\Modules\Contacts\Enums\SortField: {'Name': 1, 'Email': 2, 'Frequency': 3} }
139: * }
140: *
141: * @apiSuccessExample {json} Error response example:
142: * {
143: * Module: 'Contacts',
144: * Method: 'GetSettings',
145: * Result: false,
146: * ErrorCode: 102
147: * }
148: */
149:
150: /**
151: * Obtains list of module settings for authenticated user.
152: * @return array
153: */
154: public function GetSettings()
155: {
156: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
157: $oUser = \Aurora\System\Api::getAuthenticatedUser();
158:
159: $aResult = [
160: 'AllowAddressBooksManagement' => $this->oModuleSettings->AllowAddressBooksManagement,
161: 'ImportContactsLink' => $this->oModuleSettings->ImportContactsLink,
162: 'PrimaryEmail' => (new Enums\PrimaryEmail())->getMap(),
163: 'PrimaryPhone' => (new Enums\PrimaryPhone())->getMap(),
164: 'PrimaryAddress' => (new Enums\PrimaryAddress())->getMap(),
165: 'SortField' => (new SortField())->getMap(),
166: 'ImportExportFormats' => $this->aImportExportFormats,
167: 'SaveVcfServerModuleName' => \Aurora\System\Api::GetModuleManager()->ModuleExists('DavContacts') ? 'DavContacts' : '',
168: 'ContactsPerPage' => $this->oModuleSettings->ContactsPerPage,
169: 'ContactsSortBy' => $this->oModuleSettings->ContactsSortBy
170: ];
171:
172: if ($oUser && $oUser->isNormalOrTenant()) {
173: if (null !== $oUser->getExtendedProp(self::GetName() . '::ContactsPerPage')) {
174: $aResult['ContactsPerPage'] = $oUser->getExtendedProp(self::GetName() . '::ContactsPerPage');
175: }
176:
177: $aResult['Storages'] = self::Decorator()->GetStorages();
178: }
179:
180: return $aResult;
181: }
182:
183: public function IsDisplayedStorage($Storage)
184: {
185: return true;
186: }
187:
188: /**
189: * @deprecated since version 9.7.2
190: */
191: public function GetContactStorages()
192: {
193: return $this->Decorator()->GetStorages();
194: }
195:
196: public function GetStorageDisplayName($Storage)
197: {
198: $result = '';
199:
200: switch($Storage) {
201: case Enums\StorageType::All:
202: $result = $this->i18N('LABEL_STORAGE_ALL');
203: break;
204: case Enums\StorageType::Personal:
205: $result = $this->i18N('LABEL_STORAGE_PERSONAL');
206: break;
207: case Enums\StorageType::Collected:
208: $result = $this->i18N('LABEL_STORAGE_COLLECTED');
209: break;
210: case Enums\StorageType::Team:
211: $result = $this->i18N('LABEL_STORAGE_TEAM');
212: break;
213: case Enums\StorageType::Shared:
214: $result = $this->i18N('LABEL_STORAGE_SHARED');
215: break;
216: }
217:
218: return $result;
219: }
220:
221: protected function GetStorageDisplayNameOverride($sStorageName, $sSotrageId)
222: {
223: $result = $sStorageName;
224:
225: switch(true) {
226: case $sSotrageId === Enums\StorageType::Personal && $sStorageName === Constants::ADDRESSBOOK_DEFAULT_DISPLAY_NAME:
227: $result = $this->i18N('LABEL_STORAGE_PERSONAL');
228: break;
229: case $sSotrageId === Enums\StorageType::Collected && $sStorageName === Constants::ADDRESSBOOK_COLLECTED_DISPLAY_NAME:
230: $result = $this->i18N('LABEL_STORAGE_COLLECTED');
231: break;
232: case $sSotrageId === Enums\StorageType::Team && $sStorageName === Constants::ADDRESSBOOK_TEAM_DISPLAY_NAME:
233: $result = $this->i18N('LABEL_STORAGE_TEAM');
234: break;
235: case $sSotrageId === Enums\StorageType::Shared && $sStorageName === Constants::ADDRESSBOOK_SHARED_WITH_ALL_DISPLAY_NAME:
236: $result = $this->i18N('LABEL_STORAGE_SHARED');
237: break;
238: }
239:
240: return $result;
241: }
242:
243: public function GetStorages()
244: {
245: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
246:
247: $iUserId = \Aurora\System\Api::getAuthenticatedUserId();
248:
249: $aAddressBooks = $this->Decorator()->GetAddressBooks($iUserId);
250:
251: foreach ($aAddressBooks as &$oAddressBook) {
252: $oAddressBook['DisplayName'] = $this->GetStorageDisplayNameOverride($oAddressBook['DisplayName'], $oAddressBook['Id']);
253: }
254:
255: $aStoragesOrder = [
256: StorageType::Personal,
257: StorageType::Collected,
258: StorageType::Shared,
259: StorageType::Team
260: ];
261: return $this->sortAddressBooks($aAddressBooks, $aStoragesOrder);
262: }
263:
264: protected function sortAddressBooks($aAddressBooks, $aOrder = [])
265: {
266: $priority_books = array();
267: $non_priority_books = array();
268:
269: // Loop through the address books and check their ids
270: foreach ($aAddressBooks as $book) {
271: $id = $book['Id'];
272:
273: if (in_array($id, $aOrder)) {
274: $priority_books[] = $book;
275: } else {
276: $non_priority_books[] = $book;
277: }
278: }
279:
280: // Sort the priority books array by the order of the priority ids array
281: usort($priority_books, function ($a, $b) use ($aOrder) {
282: // Get the index of the ids in the priority ids array
283: $index_a = array_search($a['Id'], $aOrder);
284: $index_b = array_search($b['Id'], $aOrder);
285:
286: // Compare the indexes
287: return $index_a - $index_b;
288: });
289:
290: // Sort the non-priority books array by the DisplayName property in ascending order
291: usort($non_priority_books, function ($a, $b) {
292: // Compare the names
293: return strcmp($a['DisplayName'], $b['DisplayName']);
294: });
295:
296: // Merge the two arrays and return the result
297: return array_merge($priority_books, $non_priority_books);
298: }
299:
300: protected function getContactsCollection($iSortField = SortField::Name, $iSortOrder = SortOrder::ASC, $iOffset = 0, $iLimit = 20, $oFilters = null)
301: {
302: $sSortField = 'FullName';
303: $sSortFieldSecond = 'ViewEmail';
304: $sSortOrder = $iSortOrder === SortOrder::ASC ? 'asc' : 'desc';
305: switch ($iSortField) {
306: case SortField::Email:
307: $sSortField = 'ViewEmail';
308: $sSortFieldSecond = 'FullName';
309: break;
310: case SortField::Frequency:
311: $sSortField = 'AgeScore';
312: // $oFilters->select(Capsule::connection()->raw('*, (Frequency/CEIL(DATEDIFF(CURDATE() + INTERVAL 1 DAY, DateModified)/30)) as AgeScore'));
313: break;
314: case SortField::FirstName:
315: $sSortField = 'FirstName';
316: break;
317: case SortField::LastName:
318: $sSortField = 'LastName';
319: break;
320: case SortField::Name:
321: $sSortField = 'FullName';
322: break;
323: }
324: if ($iOffset > 0) {
325: $oFilters->offset($iOffset);
326: }
327: if ($iLimit > 0) {
328: $oFilters->limit($iLimit);
329: }
330:
331: $oFilters
332: ->orderBy(Capsule::connection()->raw("CASE WHEN `$sSortField` = '' THEN 1 ELSE 0 END"))
333: ->orderBy($sSortField, $sSortOrder)
334: ->orderBy($sSortFieldSecond, $sSortOrder)
335: ;
336:
337: return $oFilters->get();
338: }
339:
340: /**
341: * Resolve addressbooks numeric ids to text text ids
342: *
343: * @param mixed $oUser
344: * @param mixed $aContactsCollection
345: * @return void
346: */
347: protected function resolveAddressbooksIdsForContacts($oUser, &$aContactsCollection)
348: {
349: $aAddressbooksMap = self::Decorator()->GetStoragesMapToAddressbooks();
350: $aAddressBooks = [];
351: $aPersonalAddressBooks = Backend::Carddav()->getAddressBooksForUser(Constants::PRINCIPALS_PREFIX . $oUser->PublicId);
352: foreach ($aPersonalAddressBooks as $oAddressBook) {
353: $aAddressBooks[$oAddressBook['id']] = $oAddressBook;
354: }
355:
356: $aContactsCollection->each(function (&$contact) use ($aAddressBooks, $aAddressbooksMap) {
357: $contact->UUID = (string) $contact->UUID;
358: if (!isset($aAddressBooks[$contact->Storage])) {
359: $aAddressBooks[$contact->Storage] = Backend::Carddav()->getAddressBookById($contact->Storage);
360: }
361: $StorageTextId = false;
362: if ($aAddressBooks[$contact->Storage]) {
363: $StorageTextId = array_search($aAddressBooks[$contact->Storage]['uri'], $aAddressbooksMap);
364: }
365:
366: $contact->AddressBookId = (int) $contact->Storage;
367: $contact->Storage = $StorageTextId ? $StorageTextId : (StorageType::AddressBook . '-' . $contact->Storage);
368: });
369: }
370:
371: /**
372: * @api {post} ?/Api/ UpdateSettings
373: * @apiName UpdateSettings
374: * @apiGroup Contacts
375: * @apiDescription Updates module's settings - saves them to config.json file.
376: *
377: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
378: * @apiHeaderExample {json} Header-Example:
379: * {
380: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
381: * }
382: *
383: * @apiParam {string=Contacts} Module Module name
384: * @apiParam {string=UpdateSettings} Method Method name
385: * @apiParam {string} Parameters JSON.stringified object <br>
386: * {<br>
387: * &emsp; **ContactsPerPage** *int* Count of contacts per page.<br>
388: * }
389: *
390: * @apiParamExample {json} Request-Example:
391: * {
392: * Module: 'Contacts',
393: * Method: 'UpdateSettings',
394: * Parameters: '{ ContactsPerPage: 10 }'
395: * }
396: *
397: * @apiSuccess {object[]} Result Array of response objects.
398: * @apiSuccess {string} Result.Module Module name
399: * @apiSuccess {string} Result.Method Method name
400: * @apiSuccess {bool} Result.Result Indicates if settings were updated successfully.
401: * @apiSuccess {int} [Result.ErrorCode] Error code
402: *
403: * @apiSuccessExample {json} Success response example:
404: * {
405: * Module: 'Contacts',
406: * Method: 'UpdateSettings',
407: * Result: true
408: * }
409: *
410: * @apiSuccessExample {json} Error response example:
411: * {
412: * Module: 'Contacts',
413: * Method: 'UpdateSettings',
414: * Result: false,
415: * ErrorCode: 102
416: * }
417: */
418:
419: /**
420: * Updates module's settings - saves them to config.json file or to user settings in db.
421: * @param int $ContactsPerPage Count of contacts per page.
422: * @return boolean
423: */
424: public function UpdateSettings($ContactsPerPage)
425: {
426: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
427:
428: $bResult = false;
429:
430: $oUser = \Aurora\System\Api::getAuthenticatedUser();
431: if ($oUser) {
432: if ($oUser->isNormalOrTenant()) {
433: $oUser->setExtendedProp(self::GetName() . '::ContactsPerPage', $ContactsPerPage);
434: return CoreModule::Decorator()->UpdateUserObject($oUser);
435: }
436: if ($oUser->isAdmin()) {
437: $this->setConfig('ContactsPerPage', $ContactsPerPage);
438: $bResult = $this->saveModuleConfig();
439: }
440: }
441:
442: return $bResult;
443: }
444:
445: /**
446: * @api {post} ?/Api/ Export
447: * @apiName Export
448: * @apiGroup Contacts
449: * @apiDescription Exports specified contacts to a file with specified format.
450: *
451: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
452: * @apiHeaderExample {json} Header-Example:
453: * {
454: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
455: * }
456: *
457: * @apiParam {string=Contacts} Module Module name
458: * @apiParam {string=Export} Method Method name
459: * @apiParam {string} Parameters JSON.stringified object <br>
460: * {<br>
461: * &emsp; **Format** *string* File format that should be used for export.<br>
462: * &emsp; **Filters** *array* Filters for obtaining specified contacts.<br>
463: * &emsp; **GroupUUID** *string* UUID of group that should contain contacts for export.<br>
464: * &emsp; **ContactUUIDs** *array* List of UUIDs of contacts that should be exported.<br>
465: * }
466: *
467: * @apiParamExample {json} Request-Example:
468: * {
469: * Module: 'Contacts',
470: * Method: 'Export',
471: * Parameters: '{ Format: "csv", Filters: [], GroupUUID: "", ContactUUIDs: [] }'
472: * }
473: *
474: * @apiSuccess {object[]} Result Array of response objects.
475: * @apiSuccess {string} Result.Module Module name
476: * @apiSuccess {string} Result.Method Method name
477: * @apiSuccess {mixed} Result.Result Contents of CSV or VCF file in case of success, otherwise **false**.
478: * @apiSuccess {int} [Result.ErrorCode] Error code
479: *
480: * @apiSuccessExample {json} Success response example:
481: * contents of CSV or VCF file
482: *
483: * @apiSuccessExample {json} Error response example:
484: * {
485: * Module: 'Contacts',
486: * Method: 'Export',
487: * Result: false,
488: * ErrorCode: 102
489: * }
490: */
491:
492: /**
493: * Exports specified contacts to a file with specified format.
494: * @param string $Format File format that should be used for export.
495: * @param Builder $Filters Filters for obtaining specified contacts.
496: * @param string $GroupUUID UUID of group that should contain contacts for export.
497: * @param array $ContactUUIDs List of UUIDs of contacts that should be exported.
498: * @param bool $AddressBookId
499: */
500: public function Export($UserId, $Storage, $Format, Builder $Filters = null, $GroupUUID = '', $ContactUUIDs = [], $AddressBookId = null)
501: {
502: Api::CheckAccess($UserId);
503:
504: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
505:
506: $sOutput = '';
507:
508: if (!empty($GroupUUID)) {
509: $oGroup = self::Decorator()->GetGroup($UserId, $GroupUUID);
510: if ($oGroup) {
511: $ContactUUIDs = (is_array($ContactUUIDs) && count($ContactUUIDs) > 0) ? array_intersect(
512: $oGroup->Contacts,
513: $ContactUUIDs
514: ) : $oGroup->Contacts;
515: }
516: }
517:
518: if (is_array($ContactUUIDs)) {
519: $query = $this->getGetContactsQueryBuilder($UserId, $Storage, $AddressBookId, $Filters, false, true);
520: if ($Format === 'vcf') {
521: if (count($ContactUUIDs) > 0) {
522: $query = $query->whereIn('contacts_cards.CardId', $ContactUUIDs);
523: }
524: $rows = $query->select('carddata')->pluck('carddata')->toArray();
525: foreach ($rows as $row) {
526: $sOutput .= $row;
527: }
528: } elseif ($Format === 'csv') {
529: $oSync = new Classes\Csv\Sync();
530: if (count($ContactUUIDs) === 0) {
531: $ContactUUIDs = $query->select('CardId')->pluck('CardId')->toArray();
532: }
533: $aContacts = self::Decorator()->GetContactsByUids($UserId, $ContactUUIDs);
534: $sOutput = $oSync->Export($aContacts);
535: }
536: }
537:
538: if (is_string($sOutput) && !empty($sOutput)) {
539: $fileName = 'export';
540: $aStorages = self::Decorator()->GetStorages();
541: foreach ($aStorages as $aStorage) {
542: if ($aStorage['Id'] === $Storage) {
543: $fileName = isset($aStorage['DisplayName']) ? $aStorage['DisplayName'] : $aStorage['Id'];
544: break;
545: }
546: }
547:
548: header('Pragma: public');
549: header('Content-Type: text/csv');
550: header('Content-Disposition: attachment; filename="' . $fileName . '.' . $Format . '";');
551: header('Content-Transfer-Encoding: binary');
552: }
553:
554: echo $sOutput;
555: }
556:
557: public function GetContactAsVCF($UserId, $Contact)
558: {
559: Api::CheckAccess($UserId);
560: $oVCard = new \Sabre\VObject\Component\VCard();
561: Classes\VCard\Helper::UpdateVCardFromContact($Contact, $oVCard);
562: return $oVCard->serialize();
563: }
564:
565: /**
566: * @api {post} ?/Api/ GetGroups
567: * @apiName GetGroups
568: * @apiGroup Contacts
569: * @apiDescription Returns all groups for authenticated user.
570: *
571: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
572: * @apiHeaderExample {json} Header-Example:
573: * {
574: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
575: * }
576: *
577: * @apiParam {string=Contacts} Module Module name
578: * @apiParam {string=GetGroups} Method Method name
579: *
580: * @apiParamExample {json} Request-Example:
581: * {
582: * Module: 'Contacts',
583: * Method: 'GetGroups'
584: * }
585: *
586: * @apiSuccess {object[]} Result Array of response objects.
587: * @apiSuccess {string} Result.Module Module name
588: * @apiSuccess {string} Result.Method Method name
589: * @apiSuccess {mixed} Result.Result List of groups in case of success, otherwise **false**.
590: * @apiSuccess {int} [Result.ErrorCode] Error code
591: *
592: * @apiSuccessExample {json} Success response example:
593: * {
594: * Module: 'Contacts',
595: * Method: 'GetGroups',
596: * Result: [{ City: '', Company: '', Contacts: [], Country: '', Email: '', Fax: '', IdUser: 3,
597: * IsOrganization: false, Name: 'group_name', Phone: '', State: '', Street: '', UUID: 'uuid_value',
598: * Web: '', Zip: '' }]
599: * }
600: *
601: * @apiSuccessExample {json} Error response example:
602: * {
603: * Module: 'Contacts',
604: * Method: 'GetGroups',
605: * Result: false,
606: * ErrorCode: 102
607: * }
608: */
609:
610: /**
611: * Returns all groups for authenticated user.
612: * @return array
613: */
614: public function GetGroups($UserId = null, $UUIDs = [], $Search = '')
615: {
616: $result = [];
617: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
618:
619: Api::CheckAccess($UserId);
620:
621: $aArgs = [
622: 'UserId' => $UserId,
623: 'Storage' => StorageType::Personal,
624: 'AddressBookId' => 0
625: ];
626:
627: if ($this->populateContactArguments($aArgs)) {
628: $query = Capsule::connection()->table('contacts_cards')
629: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
630: ->select('adav_cards.id as card_id', 'carddata');
631:
632: $query->where(function ($whereQuery) use ($UserId, $aArgs, $query) {
633: $this->prepareFiltersFromStorage($UserId, StorageType::Personal, $aArgs['AddressBookId'], $query, $whereQuery);
634: })->where('IsGroup', true);
635:
636: if (is_array($UUIDs) && count($UUIDs) > 0) {
637: $query->whereIn('adav_cards.id', $UUIDs);
638: }
639:
640: if (!empty($Search)) {
641: $query->where('FullName', 'LIKE', "%$Search%");
642: }
643:
644: $groups = $query->get();
645:
646: foreach ($groups as $group) {
647: $groupObj = new Group();
648: $groupObj->Id = (int) $group->card_id;
649: $groupObj->IdUser = $UserId;
650: $groupObj->populate(Helper::GetGroupDataFromVcard(
651: \Sabre\VObject\Reader::read(
652: $group->carddata,
653: \Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES
654: ),
655: $group->card_id
656: ));
657: $result[] = $groupObj;
658: }
659: }
660:
661: return $result;
662: }
663:
664: /**
665: * @api {post} ?/Api/ GetGroup
666: * @apiName GetGroup
667: * @apiGroup Contacts
668: * @apiDescription Returns group with specified UUID.
669: *
670: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
671: * @apiHeaderExample {json} Header-Example:
672: * {
673: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
674: * }
675: *
676: * @apiParam {string=Contacts} Module Module name
677: * @apiParam {string=GetGroup} Method Method name
678: * @apiParam {string} Parameters JSON.stringified object <br>
679: * {<br>
680: * &emsp; **$UUID** *string* UUID of group to return.<br>
681: * }
682: *
683: * @apiParamExample {json} Request-Example:
684: * {
685: * Module: 'Contacts',
686: * Method: 'GetGroup',
687: * Parameters: '{ UUID: "group_uuid" }'
688: * }
689: *
690: * @apiSuccess {object[]} Result Array of response objects.
691: * @apiSuccess {string} Result.Module Module name
692: * @apiSuccess {string} Result.Method Method name
693: * @apiSuccess {mixed} Result.Result Group object in case of success, otherwise **false**.
694: * @apiSuccess {string} Result.Result.City=&quot;&quot;
695: * @apiSuccess {string} Result.Result.Company=&quot;&quot;
696: * @apiSuccess {array} Result.Result.Contacts='[]'
697: * @apiSuccess {string} Result.Result.Country=&quot;&quot;
698: * @apiSuccess {string} Result.Result.Email=&quot;&quot;
699: * @apiSuccess {string} Result.Result.Fax=&quot;&quot;
700: * @apiSuccess {int} Result.Result.IdUser=0
701: * @apiSuccess {bool} Result.Result.IsOrganization=false
702: * @apiSuccess {string} Result.Result.Name=&quot;&quot;
703: * @apiSuccess {string} Result.Result.Phone=&quot;&quot;
704: * @apiSuccess {string} Result.Result.Street=&quot;&quot;
705: * @apiSuccess {string} Result.Result.UUID=&quot;&quot;
706: * @apiSuccess {string} Result.Result.Web=&quot;&quot;
707: * @apiSuccess {string} Result.Result.Zip=&quot;&quot;
708: * @apiSuccess {int} [Result.ErrorCode] Error code
709: *
710: * @apiSuccessExample {json} Success response example:
711: * {
712: * Module: 'Contacts',
713: * Method: 'GetGroup',
714: * Result: { City: '', Company: 'group_company', Contacts: [], Country: '', Email: '', Fax: '',
715: * IdUser: 3, IsOrganization: true, Name: 'group_name', Phone:'', State:'', Street:'',
716: * UUID: 'group_uuid', Web:'', Zip: '' }
717: *
718: * @apiSuccessExample {json} Error response example:
719: * {
720: * Module: 'Contacts',
721: * Method: 'GetGroup',
722: * Result: false,
723: * ErrorCode: 102
724: * }
725: */
726:
727: /**
728: * Returns group with specified UUID.
729: * @param string $UUID UUID of group to return.
730: * @return \Aurora\Modules\Contacts\Classes\Group
731: */
732: public function GetGroup($UserId, $UUID)
733: {
734: $mResult = false;
735:
736: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
737:
738: Api::CheckAccess($UserId);
739:
740: $oUser = Api::getUserById($UserId);
741: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
742: $query = Capsule::connection()->table('contacts_cards')
743: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
744: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
745: ->select('adav_cards.id as card_id', 'adav_cards.uri as card_uri', 'adav_addressbooks.id as addressbook_id', 'carddata');
746:
747: $aArgs = [
748: 'UUID' => $UUID,
749: 'UserId' => $UserId
750: ];
751:
752: $query->where(function ($q) use ($aArgs, $query) {
753: $aArgs['Query'] = $query;
754: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
755: });
756:
757: $row = $query->where('contacts_cards.IsGroup', true)->first();
758: if ($row) {
759: if (!self::Decorator()->CheckAccessToAddressBook($oUser, $row->addressbook_id, Access::Read)) {
760: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
761: }
762:
763: $mResult = new Group();
764: $mResult->IdUser = $UserId;
765: $mResult->Id = $row->card_id;
766:
767: $mResult->populate(
768: Helper::GetGroupDataFromVcard(
769: \Sabre\VObject\Reader::read(
770: $row->carddata,
771: \Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES
772: ),
773: $row->card_uri
774: )
775: );
776:
777: $mResult->UUID = $UUID;
778: }
779: }
780:
781: return $mResult;
782: }
783:
784: /**
785: * @api {post} ?/Api/ GetContacts
786: * @apiName GetContacts
787: * @apiGroup Contacts
788: * @apiDescription Returns list of contacts for specified parameters.
789: *
790: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
791: * @apiHeaderExample {json} Header-Example:
792: * {
793: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
794: * }
795: *
796: * @apiParam {string=Contacts} Module Module name
797: * @apiParam {string=GetContacts} Method Method name
798: * @apiParam {string} Parameters JSON.stringified object <br>
799: * {<br>
800: * &emsp; **Offset** *int* Offset of contacts list.<br>
801: * &emsp; **Limit** *int* Limit of result contacts list.<br>
802: * &emsp; **SortField** *int* Name of field order by.<br>
803: * &emsp; **SortOrder** *int* Sorting direction.<br>
804: * &emsp; **Storage** *string* Storage value.<br>
805: * &emsp; **Search** *string* Search string.<br>
806: * &emsp; **GroupUUID** *string* UUID of group that should contain all returned contacts.<br>
807: * &emsp; **Filters** *array* Other conditions for obtaining contacts list.<br>
808: * }
809: *
810: * @apiParamExample {json} Request-Example:
811: * {
812: * Module: 'Contacts',
813: * Method: 'GetContacts',
814: * Parameters: '{ Offset: 0, Limit: 20, SortField: 1, SortOrder: 0, Storage: "personal",
815: * Search: "", GroupUUID: "", Filters: [] }'
816: * }
817: *
818: * @apiSuccess {object[]} Result Array of response objects.
819: * @apiSuccess {string} Result.Module Module name
820: * @apiSuccess {string} Result.Method Method name
821: * @apiSuccess {mixed} Result.Result Object with contacts data in case of success, otherwise **false**.
822: * @apiSuccess {int} Result.Result.ContactCount Count of contacts that are obtained with specified conditions.
823: * @apiSuccess {array} Result.Result.List List of contacts objects.
824: * @apiSuccess {int} [Result.ErrorCode] Error code
825: *
826: * @apiSuccessExample {json} Success response example:
827: * {
828: * Module: 'Contacts',
829: * Method: 'GetContacts',
830: * Result: '{ "ContactCount": 6, "List": [{ "UUID": "contact_uuid", "IdUser": 3, "Name": "",
831: * "Email": "contact@email.com", "Storage": "personal" }] }'
832: * }
833: *
834: * @apiSuccessExample {json} Error response example:
835: * {
836: * Module: 'Contacts',
837: * Method: 'GetContacts',
838: * Result: false,
839: * ErrorCode: 102
840: * }
841: */
842:
843: /**
844: * Returns list of contacts for specified parameters.
845: * @param string $Storage Storage type of contacts.
846: * @param int $Offset Offset of contacts list.
847: * @param int $Limit Limit of result contacts list.
848: * @param int $SortField Name of field order by.
849: * @param int $SortOrder Sorting direction.
850: * @param string $Search Search string.
851: * @param string $GroupUUID UUID of group that should contain all returned contacts.
852: * @param Builder $Filters Other conditions for obtaining contacts list.
853: * @param bool $WithGroups Indicates whether contact groups should be included in the contact list
854: * @param bool $WithoutTeamContactsDuplicates Do not show a contact from the global address book if the contact with the same email address already exists in personal address book
855: * @param bool $Suggestions
856: * @param bool $AddressBookId
857: * @return array
858: */
859: public function GetContacts($UserId, $Storage = '', $Offset = 0, $Limit = 20, $SortField = SortField::Name, $SortOrder = SortOrder::ASC, $Search = '', $GroupUUID = '', Builder $Filters = null, $WithGroups = false, $WithoutTeamContactsDuplicates = false, $Suggestions = false, $AddressBookId = null)
860: {
861: // $Storage is used by subscribers to prepare filters.
862: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
863:
864: Api::CheckAccess($UserId);
865:
866: $oUser = Api::getUserById($UserId);
867: $aContacts = [];
868: if (self::Decorator()->CheckAccessToAddressBook($oUser, $AddressBookId, Access::Read)) {
869: $query = $this->getGetContactsQueryBuilder($UserId, $Storage, $AddressBookId, $Filters, $Suggestions);
870:
871: if (!empty($Search)) {
872: $query = $query->where(function ($query) use ($Search) {
873: $query->where('FullName', 'LIKE', "%$Search%")
874: ->orWhere('PersonalEmail', 'LIKE', "%$Search%")
875: ->orWhere('BusinessEmail', 'LIKE', "%$Search%")
876: ->orWhere('OtherEmail', 'LIKE', "%$Search%")
877: ->orWhere('BusinessCompany', 'LIKE', "%$Search%");
878: });
879: }
880:
881: if (!empty($GroupUUID)) {
882: $oGroup = self::Decorator()->GetGroup($UserId, $GroupUUID);
883: if ($oGroup) {
884: $contacts = $oGroup->Contacts;
885: if (count($contacts) === 0) {
886: $contacts = [null];
887: }
888: $query->whereIn('adav_cards.id', $contacts);
889: }
890: }
891:
892: $count = $query->count();
893:
894: $aContactsCollection = $this->getContactsCollection($SortField, $SortOrder, $Offset, $Limit, $query);
895:
896: if ($Storage === StorageType::All) {
897: $personalContacsCollection = $aContactsCollection->filter(function ($contact) {
898: return !$contact->IsTeam && !$contact->Shared;
899: });
900:
901: if ($WithoutTeamContactsDuplicates) {
902: $aContactsCollection->each(function ($contact, $key) use (&$aContactsCollection, $personalContacsCollection) {
903: if ($contact->IsTeam && $personalContacsCollection->unique()->contains('ViewEmail', $contact->ViewEmail)) {
904: $aContactsCollection->forget($key);
905: } elseif ($contact->Auto) { // is collected contact
906: $aContactsCollection->each(function (&$subContact) use (&$aContactsCollection, $contact, $key) {
907: if ($subContact->IsTeam && $subContact->ViewEmail === $contact->ViewEmail) {
908: $subContact->AgeScore = $contact->AgeScore;
909: $aContactsCollection->forget($key);
910: }
911: if (!$contact->IsTeam && !$contact->Shared && !$contact->Auto && $subContact->ViewEmail === $contact->ViewEmail) {
912: $aContactsCollection->forget($key);
913: }
914: });
915: }
916: });
917: } else {
918: $aContactsCollection->each(function (&$contact, $key) use (&$aContactsCollection, $personalContacsCollection) {
919: if ($contact->IsTeam) {
920: $personalContact = $personalContacsCollection->unique()->filter(function ($subContact) use (&$contact) {
921: return strtolower($contact->ViewEmail) === strtolower($subContact->ViewEmail);
922: })->first(); // Find collected contact with same email
923:
924: if ($personalContact) {
925: $contact->Frequency = $personalContact->Frequency;
926: if ($contact->Auto) { // is collected contact
927: $aContactsCollection = $aContactsCollection->filter(function ($subContact) use ($contact) {
928: return (strtolower($subContact->ViewEmail) === strtolower($contact->ViewEmail) && !$contact->Auto) ||
929: strtolower($subContact->ViewEmail) !== strtolower($contact->ViewEmail);
930: }); // remove all collected contacts
931: }
932: }
933: }
934: });
935: }
936: }
937:
938: $this->resolveAddressbooksIdsForContacts($oUser, $aContactsCollection);
939:
940: // TODO: workaround for mobile APP
941: $aContactsCollection->each(function ($contact) use ($UserId) {
942: if (!$contact->UserId) {
943: $contact->UserId = $UserId;
944: }
945: });
946:
947: $aContacts = $aContactsCollection->toArray();
948: if ($WithGroups) {
949: $groups = self::Decorator()->GetGroups($UserId, [], $Search);
950:
951: if (is_array($groups) && count($groups) > 0) {
952: $groupContactsUuids = [];
953: $contactsUuids = [];
954: array_map(function ($item) use (&$groupContactsUuids, &$contactsUuids) {
955: if (is_array($item->Contacts) && count($item->Contacts) > 0) {
956: $groupContactsUuids[$item->UUID] = $item->Contacts;
957: $contactsUuids = array_merge($contactsUuids, $item->Contacts);
958: }
959: }, $groups);
960:
961: $groupContacts = [];
962: $contactsUuids = array_unique($contactsUuids);
963:
964: if (count($contactsUuids) > 0) {
965: foreach (self::Decorator()->GetContactsByUids($UserId, $contactsUuids) as $groupContact) {
966: $groupContacts[$groupContact->UUID] = $groupContact;
967: }
968:
969: $aGroupUsersList = [];
970:
971: foreach ($groups as $group) {
972: $aGroupContactsEmails = [];
973: if (is_array($group->Contacts)) {
974: foreach ($group->Contacts as $contactUuid) {
975: if (isset($groupContacts[$contactUuid])) {
976: $oContact = $groupContacts[$contactUuid];
977: $aGroupContactsEmails[] = $oContact->FullName ? "\"{$oContact->FullName}\" <{$oContact->ViewEmail}>" : $oContact->ViewEmail;
978: }
979: }
980:
981: $aGroupUsersList[] = [
982: 'UUID' => (string)$group->UUID,
983: 'IdUser' => $group->IdUser,
984: 'FullName' => $group->Name,
985: 'FirstName' => '',
986: 'LastName' => '',
987: 'ViewEmail' => implode(', ', $aGroupContactsEmails),
988: 'Storage' => '',
989: 'Frequency' => 0,
990: 'DateModified' => '',
991: 'IsGroup' => true,
992: ];
993: }
994: }
995: $aContacts = array_merge($aContacts, $aGroupUsersList);
996: }
997: }
998: }
999: } else {
1000: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
1001: }
1002:
1003: return [
1004: 'ContactCount' => $count,
1005: 'List' => \Aurora\System\Managers\Response::GetResponseObject(array_values($aContacts))
1006: ];
1007: }
1008:
1009: public function GetContactSuggestions($UserId, $Storage, $Limit = 20, $SortField = SortField::Name, $SortOrder = SortOrder::ASC, $Search = '', $WithGroups = false, $WithoutTeamContactsDuplicates = false, $WithUserGroups = false)
1010: {
1011: $WithoutTeamContactsDuplicates = false;
1012: // $Storage is used by subscribers to prepare filters.
1013: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1014:
1015: Api::CheckAccess($UserId);
1016:
1017: $aResult = array(
1018: 'ContactCount' => 0,
1019: 'List' => []
1020: );
1021:
1022: $aContacts = $this->Decorator()->GetContacts($UserId, $Storage, 0, $Limit, $SortField, $SortOrder, $Search, '', null, $WithGroups, $WithoutTeamContactsDuplicates, true);
1023: $aResultList = $aContacts['List'];
1024:
1025: $aResult['List'] = $aResultList;
1026: $aResult['ContactCount'] = count($aResultList);
1027:
1028: if ($WithUserGroups) {
1029: $oUser = CoreModule::Decorator()->GetUserWithoutRoleCheck($UserId);
1030: if ($oUser) {
1031: $aGroups = CoreModule::Decorator()->GetGroups($oUser->IdTenant, $Search);
1032: foreach ($aGroups['Items'] as $aGroup) {
1033: $aGroup['IsGroup'] = true;
1034: $aResult['List'][] = $aGroup;
1035:
1036: $aResult['ContactCount']++;
1037: }
1038: }
1039: }
1040:
1041: return $aResult;
1042: }
1043:
1044: /**
1045: * This method used as trigger for subscibers. Check these modules: PersonalContacts, SharedContacts, TeamContacts
1046: */
1047: public function CheckAccessToObject($User, $Contact, $Access = null)
1048: {
1049: return true;
1050: }
1051:
1052: public function CheckAccessToAddressBook($User, $AddressBookId, $Access = null)
1053: {
1054: return true;
1055: }
1056:
1057: /**
1058: * @api {post} ?/Api/ GetContact
1059: * @apiName GetContact
1060: * @apiGroup Contacts
1061: * @apiDescription Returns contact with specified UUID.
1062: *
1063: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1064: * @apiHeaderExample {json} Header-Example:
1065: * {
1066: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1067: * }
1068: *
1069: * @apiParam {string=Contacts} Module Module name
1070: * @apiParam {string=GetContact} Method Method name
1071: * @apiParam {string} Parameters JSON.stringified object <br>
1072: * {<br>
1073: * &emsp; **UUID** *string* UUID of contact to return.<br>
1074: * }
1075: *
1076: * @apiParamExample {json} Request-Example:
1077: * {
1078: * Module: 'Contacts',
1079: * Method: 'GetContact',
1080: * Parameters: '{ UUID: "contact_uuid" }'
1081: * }
1082: *
1083: * @apiSuccess {object[]} Result Array of response objects.
1084: * @apiSuccess {string} Result.Module Module name
1085: * @apiSuccess {string} Result.Method Method name
1086: * @apiSuccess {mixed} Result.Result Object with contact data in case of success, otherwise **false**.
1087: * @apiSuccess {int} [Result.ErrorCode] Error code
1088: *
1089: * @apiSuccessExample {json} Success response example:
1090: * {
1091: * Module: 'Contacts',
1092: * Method: 'GetContact',
1093: * Result: '{ "IdUser": 3, "UUID": "group_uuid", "Storage": "personal", "FullName": "", "PrimaryEmail": 0,
1094: * "PrimaryPhone": 1, "PrimaryAddress": 0, "FirstName": "", "LastName": "", "NickName": "", "Skype": "",
1095: * "Facebook": "", "PersonalEmail": "contact@email.com", "PersonalAddress": "", "PersonalCity": "",
1096: * "PersonalState": "", "PersonalZip": "", "PersonalCountry": "", "PersonalWeb": "", "PersonalFax": "",
1097: * "PersonalPhone": "", "PersonalMobile": "123-234-234", "BusinessEmail": "", "BusinessCompany": "",
1098: * "BusinessAddress": "", "BusinessCity": "", "BusinessState": "", "BusinessZip": "", "BusinessCountry": "",
1099: * "BusinessJobTitle": "", "BusinessDepartment": "", "BusinessOffice": "", "BusinessPhone": "",
1100: * "BusinessFax": "", "BusinessWeb": "", "OtherEmail": "", "Notes": "", "BirthDay": 0, "BirthMonth": 0,
1101: * "BirthYear": 0, "ETag": "", "GroupUUIDs": ["group1_uuid", "group2_uuid"] }'
1102: * }
1103: *
1104: * @apiSuccessExample {json} Error response example:
1105: * {
1106: * Module: 'Contacts',
1107: * Method: 'GetContact',
1108: * Result: false,
1109: * ErrorCode: 102
1110: * }
1111: */
1112:
1113: /**
1114: * Returns contact with specified UUID.
1115: * @param string $UUID UUID of contact to return.
1116: * @return \Aurora\Modules\Contacts\Classes\Contact
1117: */
1118: public function GetContact($UUID, $UserId = null)
1119: {
1120: $mResult = false;
1121:
1122: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1123:
1124: Api::CheckAccess($UserId);
1125:
1126: $aContacts = self::Decorator()->GetContactsByUids($UserId, [$UUID]);
1127:
1128: if (count($aContacts) > 0) {
1129: $mResult = $aContacts[0];
1130: }
1131:
1132: return $mResult;
1133: }
1134:
1135: /**
1136: * @api {post} ?/Api/ GetContactsByEmails
1137: * @apiName GetContactsByEmails
1138: * @apiGroup Contacts
1139: * @apiDescription Returns list of contacts with specified emails.
1140: *
1141: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1142: * @apiHeaderExample {json} Header-Example:
1143: * {
1144: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1145: * }
1146: *
1147: * @apiParam {string=Contacts} Module Module name
1148: * @apiParam {string=GetContactsByEmails} Method Method name
1149: * @apiParam {string} Parameters JSON.stringified object <br>
1150: * {<br>
1151: * &emsp; **Emails** *array* List of emails of contacts to return.<br>
1152: * }
1153: *
1154: * @apiParamExample {json} Request-Example:
1155: * {
1156: * Module: 'Contacts',
1157: * Method: 'GetContactsByEmails',
1158: * Parameters: '{ Emails: ["contact@email.com"] }'
1159: * }
1160: *
1161: * @apiSuccess {object[]} Result Array of response objects.
1162: * @apiSuccess {string} Result.Module Module name
1163: * @apiSuccess {string} Result.Method Method name
1164: * @apiSuccess {mixed} Result.Result List of contacts in case of success, otherwise **false**.
1165: * @apiSuccess {int} [Result.ErrorCode] Error code
1166: *
1167: * @apiSuccessExample {json} Success response example:
1168: * {
1169: * Module: 'Contacts',
1170: * Method: 'GetContactsByEmails',
1171: * Result: [{ "IdUser": 3, "UUID": "group_uuid", "Storage": "personal", "FullName": "", "PrimaryEmail": 0,
1172: * "PrimaryPhone": 1, "PrimaryAddress": 0, "FirstName": "", "LastName": "", "NickName": "", "Skype": "",
1173: * "Facebook": "", "PersonalEmail": "contact@email.com", "PersonalAddress": "", "PersonalCity": "",
1174: * "PersonalState": "", "PersonalZip": "", "PersonalCountry": "", "PersonalWeb": "", "PersonalFax": "",
1175: * "PersonalPhone": "", "PersonalMobile": "123-234-234", "BusinessEmail": "", "BusinessCompany": "",
1176: * "BusinessAddress": "", "BusinessCity": "", "BusinessState": "", "BusinessZip": "", "BusinessCountry": "",
1177: * "BusinessJobTitle": "", "BusinessDepartment": "", "BusinessOffice": "", "BusinessPhone": "",
1178: * "BusinessFax": "", "BusinessWeb": "", "OtherEmail": "", "Notes": "", "BirthDay": 0, "BirthMonth": 0,
1179: * "BirthYear": 0, "ETag": "", "GroupUUIDs": ["group1_uuid", "group2_uuid"] }]
1180: * }
1181: *
1182: * @apiSuccessExample {json} Error response example:
1183: * {
1184: * Module: 'Contacts',
1185: * Method: 'GetContactsByEmails',
1186: * Result: false,
1187: * ErrorCode: 102
1188: * }
1189: */
1190:
1191: /**
1192: * Returns list of contacts with specified emails.
1193: * @param string $Storage storage of contacts.
1194: * @param array $Emails List of emails of contacts to return.
1195: * @param int|null $AddressBookId
1196: * @return \Illuminate\Database\Eloquent\Collection|null
1197: */
1198: public function GetContactsByEmails($UserId, $Storage, $Emails, $AddressBookId = null)
1199: {
1200: $result = [];
1201: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1202:
1203: Api::CheckAccess($UserId);
1204: $oUser = Api::getUserById($UserId);
1205:
1206: if (self::Decorator()->CheckAccessToAddressBook($oUser, $AddressBookId, Access::Read)) {
1207: $filter = ContactCard::whereIn('ViewEmail', $Emails);
1208: $query = $this->getGetContactsQueryBuilder($UserId, $Storage, $AddressBookId, $filter);
1209: $result = $query->get();
1210: $this->resolveAddressbooksIdsForContacts($oUser, $result);
1211: } else {
1212: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
1213: }
1214:
1215: return $result;
1216: }
1217:
1218: /**
1219: * Returns list of contacts with specified uids.
1220: * @param int $UserId
1221: * @param array $Uids List of uids of contacts to return.
1222: * @return array
1223: */
1224: public function GetContactsByUids($UserId, $Uids)
1225: {
1226: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1227:
1228: Api::CheckAccess($UserId);
1229:
1230: $oUser = Api::getUserById($UserId);
1231:
1232: $mResult = [];
1233:
1234: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
1235: $query = Capsule::connection()->table('contacts_cards')
1236: ->select('adav_cards.id as CardId', 'adav_cards.uri as card_uri', 'adav_addressbooks.id as addressbook_id', 'contacts_cards.Properties', 'carddata', 'etag', 'core_users.Id as UserId')
1237: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
1238: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
1239: ->leftJoin('core_users', 'adav_addressbooks.principaluri', '=', Capsule::connection()->raw("CONCAT('principals/', " . Capsule::connection()->getTablePrefix() . "core_users.PublicId)"));
1240:
1241: $aArgs = [
1242: 'UUID' => $Uids,
1243: 'UserId' => $UserId
1244: ];
1245: $query->where(function ($q) use ($aArgs, $query) {
1246: $aArgs['Query'] = $query;
1247: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
1248: });
1249:
1250: $rows = $query->get();
1251: foreach($rows as $row) {
1252: if (!self::Decorator()->CheckAccessToAddressBook($oUser, $row->addressbook_id, Access::Read)) {
1253: continue;
1254: }
1255:
1256: $oContact = new Contact();
1257: $oContact->Id = $row->CardId;
1258: $oContact->InitFromVCardStr($row->UserId, $row->carddata);
1259: $oContact->ETag = \trim($row->etag, '"');
1260:
1261: $storagesMapToAddressbooks = self::Decorator()->GetStoragesMapToAddressbooks();
1262: $addressbook = Backend::Carddav()->getAddressBookById($row->addressbook_id);
1263:
1264: $key = false;
1265: if ($addressbook) {
1266: $key = array_search($addressbook['uri'], $storagesMapToAddressbooks);
1267: }
1268:
1269: $oContact->Storage = $key !== false ? $key : StorageType::AddressBook;
1270: $oContact->AddressBookId = (int) $row->addressbook_id;
1271: if ($row->Properties) {
1272: $oContact->Properties = \json_decode($row->Properties, true);
1273: }
1274: $groups = self::Decorator()->GetGroups($UserId);
1275: foreach ($groups as $group) {
1276: if (in_array($row->CardId, $group->Contacts)) {
1277: $oContact->GroupUUIDs[] = $group->UUID;
1278: }
1279: }
1280:
1281: $mResult[] = $oContact;
1282: }
1283: }
1284:
1285: return $mResult;
1286: }
1287:
1288: /**
1289: * Returns list of contacts with specified emails.
1290: * @param string $Storage storage of contacts.
1291: * @param int|null $UserId
1292: * @param Builder $Filters
1293: * @return array
1294: */
1295: public function GetContactsInfo($Storage, $UserId = null, Builder $Filters = null)
1296: {
1297: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1298:
1299: Api::CheckAccess($UserId);
1300:
1301: $aResult = [
1302: 'CTag' => 0,
1303: 'Info' => []
1304: ];
1305:
1306: $aArgs = [
1307: 'UserId' => $UserId,
1308: 'Storage' => $Storage,
1309: 'AddressBookId' => 0
1310: ];
1311:
1312: if ($this->populateContactArguments($aArgs)) {
1313: if ((int) $aArgs['AddressBookId'] > 0) {
1314: $addressbook = Backend::Carddav()->getAddressBookById($aArgs['AddressBookId']);
1315:
1316: if ($addressbook) {
1317: $aResult['CTag'] = (int) $addressbook['{http://sabredav.org/ns}sync-token'];
1318: }
1319: }
1320: $query = $this->getGetContactsQueryBuilder($UserId, $Storage, $aArgs['AddressBookId'], $Filters);
1321:
1322: $aContacts = $query->get(['UUID', 'ETag', 'Auto', 'Storage']);
1323:
1324: $storagesMapToAddressbooks = self::Decorator()->GetStoragesMapToAddressbooks();
1325:
1326: foreach ($aContacts as $oContact) {
1327: $StorageTextId = false;
1328: if (!empty($addressbook)) {
1329: $StorageTextId = array_search($addressbook['uri'], $storagesMapToAddressbooks);
1330: }
1331:
1332: /**
1333: * @var \Aurora\Modules\Contacts\Models\ContactCard $oContact
1334: */
1335: $aResult['Info'][] = [
1336: 'UUID' => (string) $oContact->UUID,
1337: 'ETag' => $oContact->ETag,
1338: 'Storage' => $StorageTextId ? $StorageTextId : (string) $oContact->Storage,
1339: 'IsTeam' => $oContact->IsTeam,
1340: 'Shared' => $oContact->Shared,
1341: ];
1342: }
1343: }
1344:
1345: return $aResult;
1346: }
1347:
1348: /**
1349: * @api {post} ?/Api/ CreateContact
1350: * @apiName CreateContact
1351: * @apiGroup Contacts
1352: * @apiDescription Creates contact with specified parameters.
1353: *
1354: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1355: * @apiHeaderExample {json} Header-Example:
1356: * {
1357: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1358: * }
1359: *
1360: * @apiParam {string=Contacts} Module Module name
1361: * @apiParam {string=CreateContact} Method Method name
1362: * @apiParam {string} Parameters JSON.stringified object <br>
1363: * {<br>
1364: * &emsp; **Contact** *object* Parameters of contact to create.<br>
1365: * }
1366: *
1367: * @apiParamExample {json} Request-Example:
1368: * {
1369: * Module: 'Contacts',
1370: * Method: 'CreateContact',
1371: * Parameters: '{ "Contact": { "UUID": "", "PrimaryEmail": 0, "PrimaryPhone": 0, "PrimaryAddress": 0,
1372: * "FullName": "second", "FirstName": "", "LastName": "", "NickName": "", "Storage": "personal",
1373: * "Skype": "", "Facebook": "", "PersonalEmail": "contact2@email.com", "PersonalAddress": "",
1374: * "PersonalCity": "", "PersonalState": "", "PersonalZip": "", "PersonalCountry": "", "PersonalWeb": "",
1375: * "PersonalFax": "", "PersonalPhone": "", "PersonalMobile": "", "BusinessEmail": "", "BusinessCompany": "",
1376: * "BusinessJobTitle": "", "BusinessDepartment": "", "BusinessOffice": "", "BusinessAddress": "",
1377: * "BusinessCity": "", "BusinessState": "", "BusinessZip": "", "BusinessCountry": "", "BusinessFax": "",
1378: * "BusinessPhone": "", "BusinessWeb": "", "OtherEmail": "", "Notes": "", "ETag": "", "BirthDay": 0,
1379: * "BirthMonth": 0, "BirthYear": 0, "GroupUUIDs": [] } }'
1380: * }
1381: *
1382: * @apiSuccess {object[]} Result Array of response objects.
1383: * @apiSuccess {string} Result.Module Module name
1384: * @apiSuccess {string} Result.Method Method name
1385: * @apiSuccess {mixed} Result.Result New contact UUID in case of success, otherwise **false**.
1386: * @apiSuccess {int} [Result.ErrorCode] Error code
1387: *
1388: * @apiSuccessExample {json} Success response example:
1389: * {
1390: * Module: 'Contacts',
1391: * Method: 'CreateContact',
1392: * Result: 'new_contact_uuid'
1393: * }
1394: *
1395: * @apiSuccessExample {json} Error response example:
1396: * {
1397: * Module: 'Contacts',
1398: * Method: 'CreateContact',
1399: * Result: false,
1400: * ErrorCode: 102
1401: * }
1402: */
1403:
1404: /**
1405: * Creates contact with specified parameters.
1406: * @param array $Contact Parameters of contact to create.
1407: * @param int $UserId Identifier of user that should own a new contact.
1408: * @return bool|string
1409: * @throws \Aurora\System\Exceptions\ApiException
1410: */
1411: public function CreateContact($Contact, $UserId = null)
1412: {
1413: Api::CheckAccess($UserId);
1414:
1415: $oUser = CoreModule::getInstance()->GetUserWithoutRoleCheck($UserId);
1416:
1417: $mResult = false;
1418:
1419: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
1420: $oContact = new Classes\Contact();
1421: $oContact->IdUser = $oUser->Id;
1422: $oContact->IdTenant = $oUser->IdTenant;
1423: $oContact->populate($Contact);
1424:
1425: $oContact->Frequency = $this->getAutocreatedContactFrequencyAndDeleteIt($oUser->Id, $oContact->ViewEmail);
1426:
1427: $oVCard = new \Sabre\VObject\Component\VCard();
1428: Helper::UpdateVCardFromContact($oContact, $oVCard);
1429:
1430: if (self::Decorator()->CheckAccessToAddressBook($oUser, $oContact->AddressBookId, Access::Write)) {
1431: $cardUri = $oContact->UUID . '.vcf';
1432: $cardETag = Backend::Carddav()->createCard($oContact->AddressBookId, $cardUri, $oVCard->serialize());
1433:
1434: if ($cardETag) {
1435: $newCard = Backend::Carddav()->getCard($oContact->AddressBookId, $cardUri);
1436: if ($newCard) {
1437: ContactCard::where('CardId', $newCard['id'])->update(['Frequency' => $oContact->Frequency]);
1438:
1439: if (is_array($oContact->GroupUUIDs) && count($oContact->GroupUUIDs) > 0) {
1440: $oGroups = self::Decorator()->GetGroups($UserId, $oContact->GroupUUIDs);
1441: if ($oGroups) {
1442: foreach ($oGroups as $oGroup) {
1443: $oGroup->Contacts = array_merge($oGroup->Contacts, [(string) $newCard['id']]);
1444:
1445: $this->UpdateGroupObject($UserId, $oGroup);
1446: }
1447: }
1448: }
1449:
1450: $mResult = [
1451: 'UUID' => (string) $newCard['id'],
1452: 'ETag' => \trim($newCard['etag'], '"')
1453: ];
1454: }
1455: }
1456: } else {
1457: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
1458: }
1459: }
1460:
1461: return $mResult;
1462: }
1463:
1464: /**
1465: * Obtains autocreated contact frequency if user have already created it.
1466: * Removes autocreated contact.
1467: * @param int $UserId User identifier.
1468: * @param string $sViewEmail View email of contact to create
1469: */
1470: private function getAutocreatedContactFrequencyAndDeleteIt($UserId, $sViewEmail)
1471: {
1472: Api::CheckAccess($UserId);
1473:
1474: $iFrequency = 0;
1475:
1476: $aArgs = [
1477: 'UserId' => $UserId,
1478: 'Storage' => StorageType::Collected,
1479: 'AddressBookId' => 0
1480: ];
1481:
1482: if ($this->populateContactArguments($aArgs)) {
1483: $oQuery = ContactCard::where([
1484: ['AddressBookId', '=', $aArgs['AddressBookId']],
1485: ['ViewEmail', '=', $sViewEmail]
1486: ]);
1487:
1488: $oAutocreatedContacts = $this->getContactsCollection(
1489: SortField::Name,
1490: SortOrder::ASC,
1491: 0,
1492: 1,
1493: $oQuery
1494: );
1495: $oContact = $oAutocreatedContacts->first();
1496: if ($oContact instanceof ContactCard) {
1497: $card_uri = Capsule::connection()->table('adav_cards')
1498: ->where('id', $oContact->CardId)
1499: ->pluck('uri')->first();
1500:
1501: Backend::Carddav()->deleteCard($oContact->AddressBookId, $card_uri);
1502: $iFrequency = $oContact->Frequency;
1503: }
1504: }
1505:
1506: return $iFrequency;
1507: }
1508:
1509: /**
1510: * @api {post} ?/Api/ UpdateContact
1511: * @apiName UpdateContact
1512: * @apiGroup Contacts
1513: * @apiDescription Updates contact with specified parameters.
1514: *
1515: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1516: * @apiHeaderExample {json} Header-Example:
1517: * {
1518: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1519: * }
1520: *
1521: * @apiParam {string=Contacts} Module Module name
1522: * @apiParam {string=UpdateContact} Method Method name
1523: * @apiParam {string} Parameters JSON.stringified object <br>
1524: * {<br>
1525: * &emsp; **Contact** *array* Parameters of contact to update.<br>
1526: * }
1527: *
1528: * @apiParamExample {json} Request-Example:
1529: * {
1530: * Module: 'Contacts',
1531: * Method: 'UpdateContact',
1532: * Parameters: '{ "Contact": { "UUID": "contact2_uuid", "PrimaryEmail": 0, "PrimaryPhone": 0,
1533: * "PrimaryAddress": 0, "FullName": "contact2", "FirstName": "", "LastName": "", "NickName": "",
1534: * "Storage": "personal", "Skype": "", "Facebook": "", "PersonalEmail": "contact2@email.com",
1535: * "PersonalAddress": "", "PersonalCity": "", "PersonalState": "", "PersonalZip": "", "PersonalCountry": "",
1536: * "PersonalWeb": "", "PersonalFax": "", "PersonalPhone": "", "PersonalMobile": "", "BusinessEmail": "",
1537: * "BusinessCompany": "", "BusinessJobTitle": "", "BusinessDepartment": "", "BusinessOffice": "",
1538: * "BusinessAddress": "", "BusinessCity": "", "BusinessState": "", "BusinessZip": "", "BusinessCountry": "",
1539: * "BusinessFax": "", "BusinessPhone": "", "BusinessWeb": "", "OtherEmail": "", "Notes": "", "ETag": "",
1540: * "BirthDay": 0, "BirthMonth": 0, "BirthYear": 0, "GroupUUIDs": [] } }'
1541: * }
1542: *
1543: * @apiSuccess {object[]} Result Array of response objects.
1544: * @apiSuccess {string} Result.Module Module name
1545: * @apiSuccess {string} Result.Method Method name
1546: * @apiSuccess {bool} Result.Result Indicates if contact was updated successfully.
1547: * @apiSuccess {int} [Result.ErrorCode] Error code
1548: *
1549: * @apiSuccessExample {json} Success response example:
1550: * {
1551: * Module: 'Contacts',
1552: * Method: 'UpdateContact',
1553: * Result: true
1554: * }
1555: *
1556: * @apiSuccessExample {json} Error response example:
1557: * {
1558: * Module: 'Contacts',
1559: * Method: 'UpdateContact',
1560: * Result: false,
1561: * ErrorCode: 102
1562: * }
1563: */
1564:
1565: /**
1566: * Updates contact with specified parameters.
1567: * @param array $Contact Parameters of contact to update.
1568: * @return array|bool
1569: */
1570: public function UpdateContact($UserId, $Contact)
1571: {
1572: Api::CheckAccess($UserId);
1573:
1574: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1575:
1576: $oContact = self::Decorator()->GetContact($Contact['UUID'], $UserId);
1577: $oUser = Api::getUserById($UserId);
1578: if ($oContact && self::Decorator()->CheckAccessToObject($oUser, $oContact, Access::Write)) {
1579: $oContact->populate($Contact);
1580: $result = self::Decorator()->UpdateContactObject($oContact);
1581: if ($result) {
1582: if (is_array($oContact->GroupUUIDs)) {
1583: $groups = self::Decorator()->GetGroups($UserId);
1584: foreach ($groups as $group) {
1585: if ($group) {
1586: if (!in_array($group->UUID, $oContact->GroupUUIDs)) {
1587: $group->Contacts = array_diff($group->Contacts, [$oContact->UUID]);
1588: } else {
1589: $group->Contacts = array_merge($group->Contacts, [$oContact->UUID]);
1590: }
1591: $this->UpdateGroupObject($UserId, $group);
1592: }
1593: }
1594: }
1595:
1596: return [
1597: 'UUID' => (string) $oContact->UUID,
1598: 'ETag' => $result
1599: ];
1600: } else {
1601: return false;
1602: }
1603: } else {
1604: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
1605: }
1606:
1607: return false;
1608: }
1609:
1610: public function MoveContactsToStorage($UserId, $FromStorage, $ToStorage, $UUIDs)
1611: {
1612: $result = false;
1613:
1614: if ($ToStorage === StorageType::Team) { // skip moving to team storage
1615: return false;
1616: }
1617:
1618: $query = Capsule::connection()
1619: ->table('contacts_cards')
1620: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
1621: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
1622: ->select('adav_cards.uri as card_uri', 'adav_cards.id as card_id', 'adav_addressbooks.id as addressbook_id');
1623:
1624: $aArgs = [
1625: 'UserId' => $UserId,
1626: 'UUID' => $UUIDs
1627: ];
1628:
1629: // build a query to obtain the card_uri and card_id with checking access to the contact
1630: $cardsUris = $query->where(function ($q) use ($aArgs, $query) {
1631: $aArgs['Query'] = $query;
1632: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
1633: })->pluck('card_uri', 'card_id')->toArray();
1634:
1635: $aArgsTo = [
1636: 'UserId' => $UserId,
1637: 'Storage' => $ToStorage,
1638: 'AddressBookId' => 0
1639: ];
1640:
1641: $resultFrom = true;
1642: $resultTo = $this->populateContactArguments($aArgsTo);
1643:
1644: $ToAddressBookId = (int) $aArgsTo['AddressBookId']; // getting ToAddressBookId from ToStorage
1645:
1646: foreach ($cardsUris as $cardId => $cardUri) {
1647: $FromAddressBookId = 0;
1648: if ($FromStorage === StorageType::All) { // getting $FromAddressBookId from the contact
1649: $oContact = self::Decorator()->GetContact($cardId, $UserId);
1650: if ($oContact instanceof Contact) {
1651: if ($oContact->Storage === StorageType::Team) { // skip the team contact
1652: continue;
1653: }
1654: $FromAddressBookId = (int) $oContact->AddressBookId;
1655: }
1656: } else {
1657: $aArgsFrom = [
1658: 'UserId' => $UserId,
1659: 'Storage' => $FromStorage,
1660: 'AddressBookId' => 0
1661: ];
1662:
1663: $resultFrom = $this->populateContactArguments($aArgsFrom);
1664:
1665: $FromAddressBookId = (int) $aArgsFrom['AddressBookId'];
1666: }
1667: if ($FromAddressBookId != $ToAddressBookId && $resultFrom && $resultTo) { // do not allow contact to be moved to its own storage
1668: $result = Backend::Carddav()->updateCardAddressBook($FromAddressBookId, $ToAddressBookId, $cardUri);
1669: }
1670: }
1671:
1672: return $result;
1673: }
1674:
1675: /**
1676: * !Not public
1677: * This method is restricted to be called by web API (see denyMethodsCallByWebApi method).
1678: * @param Contact $Contact
1679: * @return string|bool
1680: */
1681: public function UpdateContactObject($Contact)
1682: {
1683: $mResult = false;
1684:
1685: $oUser = \Aurora\System\Api::getAuthenticatedUser();
1686: $aStorageParts = \explode('-', $Contact->Storage);
1687: if (isset($aStorageParts[0], $aStorageParts[1]) && $aStorageParts[0] === StorageType::AddressBook) {
1688: $Contact->AddressBookId = (int) $aStorageParts[1];
1689: $Contact->Storage = StorageType::AddressBook;
1690: }
1691:
1692: $query = Capsule::connection()->table('contacts_cards')
1693: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
1694: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
1695: ->select('adav_cards.uri as card_uri', 'adav_addressbooks.id as addressbook_id', 'carddata');
1696:
1697: $aArgs = [
1698: 'UserId' => $oUser->Id,
1699: 'UUID' => $Contact->Id
1700: ];
1701:
1702: // build a query to obtain the addressbook_id and card_uri with checking access to the contact
1703: $query->where(function ($q) use ($aArgs, $query) {
1704: $aArgs['Query'] = $query;
1705: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
1706: });
1707:
1708: $row = $query->first();
1709: if ($row) {
1710: $oVCard = \Sabre\VObject\Reader::read($row->carddata);
1711: $uidVal = $oVCard->UID->getValue();
1712: if (empty($uidVal) || is_numeric($uidVal)) {
1713: $uriInfo = pathinfo($row->card_uri);
1714: if (isset($uriInfo['filename'])) {
1715: $oVCard->UID = $uriInfo['filename'];
1716: }
1717: }
1718:
1719: Helper::UpdateVCardFromContact($Contact, $oVCard);
1720: $mResult = Backend::Carddav()->updateCard($row->addressbook_id, $row->card_uri, $oVCard->serialize());
1721: $mResult = str_replace('"', '', $mResult);
1722: }
1723:
1724: return $mResult;
1725: }
1726:
1727: /**
1728: * @api {post} ?/Api/ DeleteContacts
1729: * @apiName DeleteContacts
1730: * @apiGroup Contacts
1731: * @apiDescription Deletes contacts with specified UUIDs.
1732: *
1733: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1734: * @apiHeaderExample {json} Header-Example:
1735: * {
1736: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1737: * }
1738: *
1739: * @apiParam {string=Contacts} Module Module name
1740: * @apiParam {string=DeleteContacts} Method Method name
1741: * @apiParam {string} Parameters JSON.stringified object <br>
1742: * {<br>
1743: * &emsp; **UUIDs** *array* Array of strings - UUIDs of contacts to delete.<br>
1744: * }
1745: *
1746: * @apiParamExample {json} Request-Example:
1747: * {
1748: * Module: 'Contacts',
1749: * Method: 'DeleteContacts',
1750: * Parameters: '{ UUIDs: ["uuid1", "uuid"] }'
1751: * }
1752: *
1753: * @apiSuccess {object[]} Result Array of response objects.
1754: * @apiSuccess {string} Result.Module Module name
1755: * @apiSuccess {string} Result.Method Method name
1756: * @apiSuccess {bool} Result.Result Indicates if contacts were deleted successfully.
1757: * @apiSuccess {int} [Result.ErrorCode] Error code
1758: *
1759: * @apiSuccessExample {json} Success response example:
1760: * {
1761: * Module: 'Contacts',
1762: * Method: 'DeleteContacts',
1763: * Result: true
1764: * }
1765: *
1766: * @apiSuccessExample {json} Error response example:
1767: * {
1768: * Module: 'Contacts',
1769: * Method: 'DeleteContacts',
1770: * Result: false,
1771: * ErrorCode: 102
1772: * }
1773: */
1774:
1775: /**
1776: * Deletes contacts with specified UUIDs.
1777: * @param int $UserId
1778: * @param string $Storage
1779: * @param array $UUIDs Array of strings - UUIDs of contacts to delete.
1780: * @return bool
1781: */
1782: public function DeleteContacts($UserId, $Storage, $UUIDs)
1783: {
1784: $mResult = false;
1785: Api::CheckAccess($UserId);
1786: $oUser = Api::getUserById($UserId);
1787:
1788: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1789:
1790: $AddressBookId = $Storage; // It's trick for API compatibility. Method should accept numeric AddressBookId, but clients sends storage name as ID
1791: if (self::Decorator()->CheckAccessToAddressBook($oUser, $AddressBookId, Enums\Access::Write)) {
1792: $query = Capsule::connection()->table('contacts_cards')
1793: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
1794: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
1795: ->select('adav_cards.id as card_id', 'adav_cards.uri as card_uri', 'adav_addressbooks.id as addressbook_id')
1796: ->where('adav_cards.addressbookid', '=', $AddressBookId);
1797:
1798: $aArgs = [
1799: 'UUID' => $UUIDs,
1800: 'UserId' => $UserId
1801: ];
1802: $query->where(function ($q) use ($aArgs, $query) {
1803: $aArgs['Query'] = $query;
1804: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
1805: });
1806:
1807: $rows = $query->distinct()->get()->all();
1808:
1809: $groups = self::Decorator()->GetGroups($UserId);
1810: $groupsToUpdate = [];
1811:
1812: foreach ($rows as $row) {
1813: Backend::Carddav()->deleteCard($row->addressbook_id, $row->card_uri);
1814: foreach ($groups as $group) {
1815: if (($key = array_search($row->card_id, $group->Contacts)) !== false) {
1816: unset($group->Contacts[$key]);
1817: if (!in_array($group->UUID, $groupsToUpdate)) {
1818: $groupsToUpdate[] = $group->UUID;
1819: }
1820: }
1821: }
1822: }
1823:
1824: foreach ($groups as $group) {
1825: if (in_array($group->UUID, $groupsToUpdate)) {
1826: $this->UpdateGroupObject($UserId, $group);
1827: }
1828: }
1829:
1830: $mResult = true;
1831: } else {
1832: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
1833: }
1834:
1835: return $mResult;
1836: }
1837:
1838: /**
1839: * @api {post} ?/Api/ CreateGroup
1840: * @apiName CreateGroup
1841: * @apiGroup Contacts
1842: * @apiDescription Creates group with specified parameters.
1843: *
1844: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1845: * @apiHeaderExample {json} Header-Example:
1846: * {
1847: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1848: * }
1849: *
1850: * @apiParam {string=Contacts} Module Module name
1851: * @apiParam {string=CreateGroup} Method Method name
1852: * @apiParam {string} Parameters JSON.stringified object <br>
1853: * {<br>
1854: * &emsp; **Group** *object* Parameters of group to create.<br>
1855: * }
1856: *
1857: * @apiParamExample {json} Request-Example:
1858: * {
1859: * Module: 'Contacts',
1860: * Method: 'CreateGroup',
1861: * Parameters: '{ "Group": { "UUID": "", "Name": "new_group_name", "IsOrganization": "0", "Email": "",
1862: * "Country": "", "City": "", "Company": "", "Fax": "", "Phone": "", "State": "", "Street": "",
1863: * "Web": "", "Zip": "", "Contacts": [] } }'
1864: * }
1865: *
1866: * @apiSuccess {object[]} Result Array of response objects.
1867: * @apiSuccess {string} Result.Module Module name
1868: * @apiSuccess {string} Result.Method Method name
1869: * @apiSuccess {mixed} Result.Result New group UUID in case of success, otherwise **false**.
1870: * @apiSuccess {int} [Result.ErrorCode] Error code
1871: *
1872: * @apiSuccessExample {json} Success response example:
1873: * {
1874: * Module: 'Contacts',
1875: * Method: 'CreateGroup',
1876: * Result: 'new_group_uuid'
1877: * }
1878: *
1879: * @apiSuccessExample {json} Error response example:
1880: * {
1881: * Module: 'Contacts',
1882: * Method: 'CreateGroup',
1883: * Result: false,
1884: * ErrorCode: 102
1885: * }
1886: */
1887:
1888: /**
1889: * Creates group with specified parameters.
1890: * @param array $Group Parameters of group to create.
1891: * @return string|bool
1892: */
1893: public function CreateGroup($Group, $UserId = null)
1894: {
1895: $mResult = false;
1896:
1897: Api::CheckAccess($UserId);
1898:
1899: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
1900:
1901: if (is_array($Group)) {
1902: \Aurora\System\Validator::validate($Group, [
1903: 'Name' => 'required'
1904: ], [
1905: 'required' => 'The :attribute field is required.'
1906: ]);
1907:
1908: $oGroup = new Classes\Group();
1909: $oGroup->IdUser = (int) $UserId;
1910:
1911: $oGroup->populate($Group);
1912: if (isset($Group['Contacts']) && is_array($Group['Contacts'])) {
1913: $oGroup->Contacts = $this->getContactsUUIDsFromIds($UserId, $Group['Contacts']);
1914: }
1915:
1916: $oVCard = new \Sabre\VObject\Component\VCard();
1917: Helper::UpdateVCardFromGroup($oGroup, $oVCard);
1918:
1919: $userPublicId = Api::getUserPublicIdById($UserId);
1920: $addressBook = Backend::Carddav()->getAddressBookForUser(Constants::PRINCIPALS_PREFIX . $userPublicId, Constants::ADDRESSBOOK_DEFAULT_NAME);
1921: $cardUri = $oGroup->UUID . '.vcf';
1922:
1923: if ($addressBook) {
1924: $cardETag = Backend::Carddav()->createCard($addressBook['id'], $cardUri, $oVCard->serialize());
1925: if ($cardETag) {
1926: $newCard = Backend::Carddav()->getCard($addressBook['id'], $cardUri);
1927: if ($newCard) {
1928: $mResult = [
1929: 'UUID' => (string) $newCard['id'],
1930: 'ETag' => \trim($newCard['etag'], '"')
1931: ];
1932: }
1933: }
1934: }
1935: }
1936:
1937: return $mResult;
1938: }
1939:
1940: protected function getContactsUUIDsFromIds($UserId, $Ids)
1941: {
1942: if (is_array($Ids) && count($Ids) > 0) {
1943: $query = Capsule::connection()->table('contacts_cards')
1944: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
1945: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
1946: ->select('adav_cards.uri as card_uri');
1947:
1948: $aArgs = [
1949: 'UserId' => $UserId,
1950: 'UUID' => $Ids
1951: ];
1952:
1953: // build a query to obtain the addressbook_id and card_uri with checking access to the contact
1954: $query->where(function ($q) use ($aArgs, $query) {
1955: $aArgs['Query'] = $query;
1956: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
1957: });
1958:
1959: $contactsIds = $query->pluck('card_uri')->all();
1960:
1961: return array_map(function ($item) {
1962: $pathInfo = pathinfo($item);
1963: return $pathInfo['filename'];
1964: }, $contactsIds);
1965: } else {
1966: return [];
1967: }
1968: }
1969:
1970: protected function getContactsIdsFromUUIDs($UserId, $UUIDs)
1971: {
1972: $Uris = array_map(function ($item) {
1973: return $item . '.vcf';
1974: }, $UUIDs);
1975:
1976: $contactsIds = Capsule::connection()->table('adav_cards')
1977: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
1978: ->select('adav_cards.id as card_id')
1979: ->where('principaluri', Constants::PRINCIPALS_PREFIX . Api::getUserPublicIdById($UserId))
1980: ->whereIn('adav_cards.uri', $Uris)->get()->all();
1981:
1982: return array_map(function ($item) {
1983: return $item->card_id;
1984: }, $contactsIds);
1985: }
1986:
1987: /**
1988: * @api {post} ?/Api/ UpdateGroup
1989: * @apiName UpdateGroup
1990: * @apiGroup Contacts
1991: * @apiDescription Updates group with specified parameters.
1992: *
1993: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
1994: * @apiHeaderExample {json} Header-Example:
1995: * {
1996: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
1997: * }
1998: *
1999: * @apiParam {string=Contacts} Module Module name
2000: * @apiParam {string=UpdateGroup} Method Method name
2001: * @apiParam {string} Parameters JSON.stringified object <br>
2002: * {<br>
2003: * &emsp; **Group** *object* Parameters of group to update.<br>
2004: * }
2005: *
2006: * @apiParamExample {json} Request-Example:
2007: * {
2008: * Module: 'Contacts',
2009: * Method: 'UpdateGroup',
2010: * Parameters: '{ "Group": { "UUID": "group_uuid", "Name": "group_name", "IsOrganization": "0",
2011: * "Email": "", "Country": "", "City": "", "Company": "", "Fax": "", "Phone": "", "State": "",
2012: * "Street": "", "Web": "", "Zip": "", "Contacts": [] } }'
2013: * }
2014: *
2015: * @apiSuccess {object[]} Result Array of response objects.
2016: * @apiSuccess {string} Result.Module Module name
2017: * @apiSuccess {string} Result.Method Method name
2018: * @apiSuccess {bool} Result.Result Indicates if group was updated successfully.
2019: * @apiSuccess {int} [Result.ErrorCode] Error code
2020: *
2021: * @apiSuccessExample {json} Success response example:
2022: * {
2023: * Module: 'Contacts',
2024: * Method: 'UpdateGroup',
2025: * Result: true
2026: * }
2027: *
2028: * @apiSuccessExample {json} Error response example:
2029: * {
2030: * Module: 'Contacts',
2031: * Method: 'UpdateGroup',
2032: * Result: false,
2033: * ErrorCode: 102
2034: * }
2035: */
2036:
2037: protected function UpdateGroupObject($UserId, $oGroup)
2038: {
2039: $mResult = false;
2040:
2041: if (is_array($oGroup->Contacts) && count($oGroup->Contacts)) {
2042: $oGroup->Contacts = $this->getContactsUUIDsFromIds($UserId, $oGroup->Contacts);
2043: }
2044:
2045: $query = Capsule::connection()->table('contacts_cards')
2046: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
2047: ->join('adav_addressbooks', 'adav_cards.addressbookid', '=', 'adav_addressbooks.id')
2048: ->select('adav_cards.uri as card_uri', 'adav_addressbooks.id as addressbook_id', 'carddata');
2049:
2050: $aArgs = [
2051: 'UserId' => $UserId,
2052: 'UUID' => $oGroup->UUID
2053: ];
2054:
2055: // build a query to obtain the addressbook_id and card_uri with checking access to the contact
2056: $query->where(function ($q) use ($aArgs, $query) {
2057: $aArgs['Query'] = $query;
2058: $this->broadcastEvent(self::GetName() . '::ContactQueryBuilder', $aArgs, $q);
2059: });
2060:
2061: $row = $query->first();
2062: if ($row) {
2063: $oVCard = \Sabre\VObject\Reader::read($row->carddata);
2064: $uidVal = $oVCard->UID->getValue();
2065: if (empty($uidVal) || is_numeric($uidVal)) {
2066: $uriInfo = pathinfo($row->card_uri);
2067: if (isset($uriInfo['filename'])) {
2068: $oVCard->UID = $uriInfo['filename'];
2069: }
2070: }
2071: Helper::UpdateVCardFromGroup($oGroup, $oVCard);
2072: $mResult = !!Backend::Carddav()->updateCard($row->addressbook_id, $row->card_uri, $oVCard->serialize());
2073: }
2074:
2075: return $mResult;
2076: }
2077:
2078: /**
2079: * Updates group with specified parameters.
2080: * @param array $Group Parameters of group to update.
2081: * @return boolean
2082: */
2083: public function UpdateGroup($UserId, $Group)
2084: {
2085: $mResult = false;
2086:
2087: Api::CheckAccess($UserId);
2088:
2089: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2090:
2091: $oGroup = self::Decorator()->GetGroup($UserId, $Group['UUID']);
2092: if ($oGroup) {
2093: $oGroup->populate($Group);
2094: $mResult = $this->UpdateGroupObject($UserId, $oGroup);
2095: }
2096:
2097: return $mResult;
2098: }
2099:
2100: /**
2101: * @api {post} ?/Api/ DeleteGroup
2102: * @apiName DeleteGroup
2103: * @apiGroup Contacts
2104: * @apiDescription Deletes group with specified UUID.
2105: *
2106: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
2107: * @apiHeaderExample {json} Header-Example:
2108: * {
2109: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
2110: * }
2111: *
2112: * @apiParam {string=Contacts} Module Module name
2113: * @apiParam {string=DeleteGroup} Method Method name
2114: * @apiParam {string} Parameters JSON.stringified object <br>
2115: * {<br>
2116: * &emsp; **UUID** *string* UUID of group to delete.<br>
2117: * }
2118: *
2119: * @apiParamExample {json} Request-Example:
2120: * {
2121: * Module: 'Contacts',
2122: * Method: 'DeleteGroup',
2123: * Parameters: '{ UUID: "group_uuid" }'
2124: * }
2125: *
2126: * @apiSuccess {object[]} Result Array of response objects.
2127: * @apiSuccess {string} Result.Module Module name
2128: * @apiSuccess {string} Result.Method Method name
2129: * @apiSuccess {bool} Result.Result Indicates if group was deleted successfully.
2130: * @apiSuccess {int} [Result.ErrorCode] Error code
2131: *
2132: * @apiSuccessExample {json} Success response example:
2133: * {
2134: * Module: 'Contacts',
2135: * Method: 'DeleteGroup',
2136: * Result: true
2137: * }
2138: *
2139: * @apiSuccessExample {json} Error response example:
2140: * {
2141: * Module: 'Contacts',
2142: * Method: 'DeleteGroup',
2143: * Result: false,
2144: * ErrorCode: 102
2145: * }
2146: */
2147:
2148: /**
2149: * Deletes group with specified UUID.
2150: * @param string $UUID UUID of group to delete.
2151: * @return bool
2152: */
2153: public function DeleteGroup($UserId, $UUID)
2154: {
2155: Api::CheckAccess($UserId);
2156:
2157: return self::Decorator()->DeleteContacts($UserId, StorageType::Personal, [$UUID]);
2158: }
2159:
2160: /**
2161: * @api {post} ?/Api/ AddContactsToGroup
2162: * @apiName AddContactsToGroup
2163: * @apiGroup Contacts
2164: * @apiDescription Adds specified contacts to specified group.
2165: *
2166: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
2167: * @apiHeaderExample {json} Header-Example:
2168: * {
2169: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
2170: * }
2171: *
2172: * @apiParam {string=Contacts} Module Module name
2173: * @apiParam {string=AddContactsToGroup} Method Method name
2174: * @apiParam {string} Parameters JSON.stringified object <br>
2175: * {<br>
2176: * &emsp; **GroupUUID** *string* Id of the group.<br>
2177: * &emsp; **ContactUUIDs** *array* Array of strings - IDs of contacts to add to group.<br>
2178: * }
2179: *
2180: * @apiParamExample {json} Request-Example:
2181: * {
2182: * Module: 'Contacts',
2183: * Method: 'AddContactsToGroup',
2184: * Parameters: '{ GroupUUID: "group_id", ContactUUIDs: ["contact1_id", "contact2_id"] }'
2185: * }
2186: *
2187: * @apiSuccess {object[]} Result Array of response objects.
2188: * @apiSuccess {string} Result.Module Module name
2189: * @apiSuccess {string} Result.Method Method name
2190: * @apiSuccess {bool} Result.Result Indicates if contacts were successfully added to group.
2191: * @apiSuccess {int} [Result.ErrorCode] Error code
2192: *
2193: * @apiSuccessExample {json} Success response example:
2194: * {
2195: * Module: 'Contacts',
2196: * Method: 'AddContactsToGroup',
2197: * Result: true
2198: * }
2199: *
2200: * @apiSuccessExample {json} Error response example:
2201: * {
2202: * Module: 'Contacts',
2203: * Method: 'AddContactsToGroup',
2204: * Result: false,
2205: * ErrorCode: 102
2206: * }
2207: */
2208:
2209: /**
2210: * Adds specified contacts to specified group.
2211: *
2212: * @param string $GroupUUID ID of group.
2213: * @param array $ContactUUIDs Array of strings - IDs of contacts to add to group.
2214: *
2215: * @return boolean
2216: */
2217: public function AddContactsToGroup($UserId, $GroupUUID, $ContactUUIDs)
2218: {
2219: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2220:
2221: $mResult = false;
2222:
2223: Api::CheckAccess($UserId);
2224:
2225: // currently method accepts Ids (not UUIDs), argument names are kept for compatibility
2226: $GroupId = $GroupUUID;
2227: $ContactIds = $ContactUUIDs;
2228:
2229: if (is_array($ContactIds) && !empty($ContactIds)) {
2230:
2231: $oGroup = self::Decorator()->GetGroup($UserId, $GroupId);
2232: if ($oGroup) {
2233: //getting contacts by ids is needed here just for making sure that they exist
2234: $aContacts = self::Decorator()->GetContactsByUids($UserId, $ContactIds);
2235: $newContactIds = array_map(function ($item) {
2236: return $item->Id;
2237: }, $aContacts);
2238: $oGroup->Contacts = array_merge($oGroup->Contacts, $newContactIds);
2239:
2240: $mResult = $this->UpdateGroupObject($UserId, $oGroup);
2241: }
2242: }
2243:
2244: return $mResult;
2245: }
2246:
2247: /**
2248: * @api {post} ?/Api/ RemoveContactsFromGroup
2249: * @apiName RemoveContactsFromGroup
2250: * @apiGroup Contacts
2251: * @apiDescription Removes specified contacts from specified group.
2252: *
2253: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
2254: * @apiHeaderExample {json} Header-Example:
2255: * {
2256: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
2257: * }
2258: *
2259: * @apiParam {string=Contacts} Module Module name
2260: * @apiParam {string=RemoveContactsFromGroup} Method Method name
2261: * @apiParam {string} Parameters JSON.stringified object <br>
2262: * {<br>
2263: * &emsp; **GroupUUID** *string* ID of group.<br>
2264: * &emsp; **ContactUUIDs** *array* Array of strings - IDs of contacts to remove from group.<br>
2265: * }
2266: *
2267: * @apiParamExample {json} Request-Example:
2268: * {
2269: * Module: 'Contacts',
2270: * Method: 'RemoveContactsFromGroup',
2271: * Parameters: '{ GroupUUID: "group_id", ContactUUIDs: ["contact1_id", "contact2_id"] }'
2272: * }
2273: *
2274: * @apiSuccess {object[]} Result Array of response objects.
2275: * @apiSuccess {string} Result.Module Module name
2276: * @apiSuccess {string} Result.Method Method name
2277: * @apiSuccess {bool} Result.Result Indicates if contacts were successfully removed from group.
2278: * @apiSuccess {int} [Result.ErrorCode] Error code
2279: *
2280: * @apiSuccessExample {json} Success response example:
2281: * {
2282: * Module: 'Contacts',
2283: * Method: 'RemoveContactsFromGroup',
2284: * Result: true
2285: * }
2286: *
2287: * @apiSuccessExample {json} Error response example:
2288: * {
2289: * Module: 'Contacts',
2290: * Method: 'RemoveContactsFromGroup',
2291: * Result: false,
2292: * ErrorCode: 102
2293: * }
2294: */
2295:
2296: /**
2297: * Removes specified contacts from specified group.
2298: * @param string $GroupUUID ID of group.
2299: * @param array $ContactUUIDs Array of strings - IDs of contacts to remove from group.
2300: * @return boolean
2301: */
2302: public function RemoveContactsFromGroup($UserId, $GroupUUID, $ContactUUIDs)
2303: {
2304: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2305:
2306: Api::CheckAccess($UserId);
2307: $mResult = false;
2308:
2309: // currently method accepts Ids (not UUIDs), argument names are kept for compatibility
2310: $GroupId = $GroupUUID;
2311: $ContactIds = $ContactUUIDs;
2312:
2313: if (is_array($ContactIds) && !empty($ContactIds)) {
2314: $oGroup = self::Decorator()->GetGroup($UserId, $GroupId);
2315: if ($oGroup) {
2316: $ContactIds = array_map(function ($id) {
2317: return (int) $id;
2318: }, $ContactIds);
2319: $oGroup->Contacts = array_diff($oGroup->Contacts, $ContactIds);
2320: $mResult = $this->UpdateGroupObject($UserId, $oGroup);
2321: }
2322: }
2323:
2324: return $mResult;
2325: }
2326:
2327: /**
2328: * @api {post} ?/Api/ Import
2329: * @apiName Import
2330: * @apiGroup Contacts
2331: * @apiDescription Imports contacts from file with specified format.
2332: *
2333: * @apiHeader {string} Authorization "Bearer " + Authentication token which was received as the result of Core.Login method.
2334: * @apiHeaderExample {json} Header-Example:
2335: * {
2336: * "Authorization": "Bearer 32b2ecd4a4016fedc4abee880425b6b8"
2337: * }
2338: *
2339: * @apiParam {string=Contacts} Module Module name
2340: * @apiParam {string=Import} Method Method name
2341: * @apiParam {string} Parameters JSON.stringified object <br>
2342: * {<br>
2343: * &emsp; **UploadData** *array* Array of uploaded file data.<br>
2344: * &emsp; **Storage** *string* Storage name.<br>
2345: * &emsp; **GroupUUID** *array* Group UUID.<br>
2346: * }
2347: *
2348: * @apiParamExample {json} Request-Example:
2349: * {
2350: * Module: 'Contacts',
2351: * Method: 'Import',
2352: * Parameters: '{ "UploadData": { "tmp_name": "tmp_name_value", "name": "name_value" },
2353: * "Storage": "personal", "GroupUUID": "" }'
2354: * }
2355: *
2356: * @apiSuccess {object[]} Result Array of response objects.
2357: * @apiSuccess {string} Result.Module Module name
2358: * @apiSuccess {string} Result.Method Method name
2359: * @apiSuccess {mixed} Result.Result Object with counts of imported and parsed contacts in case of success, otherwise **false**.
2360: * @apiSuccess {int} [Result.ErrorCode] Error code
2361: *
2362: * @apiSuccessExample {json} Success response example:
2363: * {
2364: * Module: 'Contacts',
2365: * Method: 'Import',
2366: * Result: { "ImportedCount" : 2, "ParsedCount": 3}
2367: * }
2368: *
2369: * @apiSuccessExample {json} Error response example:
2370: * {
2371: * Module: 'Contacts',
2372: * Method: 'Import',
2373: * Result: false,
2374: * ErrorCode: 102
2375: * }
2376: */
2377:
2378: /**
2379: * Imports contacts from file with specified format.
2380: * @param array $UploadData Array of uploaded file data.
2381: * @param array $GroupUUID Group UUID.
2382: * @return array
2383: * @throws \Aurora\System\Exceptions\ApiException
2384: */
2385: public function Import($UserId, $UploadData, $GroupUUID, $Storage = null)
2386: {
2387: Api::CheckAccess($UserId);
2388:
2389: $oUser = CoreModule::getInstance()->GetUserWithoutRoleCheck($UserId);
2390:
2391: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2392:
2393: $aResponse = array(
2394: 'ImportedCount' => 0,
2395: 'ParsedCount' => 0
2396: );
2397:
2398: if (is_array($UploadData)) {
2399: $oApiFileCacheManager = new \Aurora\System\Managers\Filecache();
2400: $sTempFileName = 'import-post-' . md5($UploadData['name'] . $UploadData['tmp_name']);
2401: if ($oApiFileCacheManager->moveUploadedFile($oUser->UUID, $sTempFileName, $UploadData['tmp_name'], '', self::GetName())) {
2402: $sTempFilePath = $oApiFileCacheManager->generateFullFilePath($oUser->UUID, $sTempFileName, '', self::GetName());
2403:
2404: $aImportResult = array();
2405:
2406: $sFileExtension = strtolower(\Aurora\System\Utils::GetFileExtension($UploadData['name']));
2407: switch ($sFileExtension) {
2408: case 'csv':
2409: $oSync = new Classes\Csv\Sync();
2410: $aImportResult = $oSync->Import($oUser->Id, $sTempFilePath, $GroupUUID, $Storage);
2411: break;
2412: case 'vcf':
2413: $aImportResult = $this->importVcf($oUser->Id, $sTempFilePath, $Storage);
2414: break;
2415: }
2416:
2417: if (is_array($aImportResult) && isset($aImportResult['ImportedCount']) && isset($aImportResult['ParsedCount'])) {
2418: $aResponse['ImportedCount'] = $aImportResult['ImportedCount'];
2419: $aResponse['ParsedCount'] = $aImportResult['ParsedCount'];
2420: } else {
2421: throw new ApiException(Notifications::IncorrectFileExtension);
2422: }
2423:
2424: $oApiFileCacheManager->clear($oUser->UUID, $sTempFileName, '', self::GetName());
2425: } else {
2426: throw new ApiException(Notifications::UnknownError);
2427: }
2428: } else {
2429: throw new ApiException(Notifications::UnknownError);
2430: }
2431:
2432: return $aResponse;
2433: }
2434:
2435: public function UpdateSharedContacts($UserId, $UUIDs)
2436: {
2437: Api::CheckAccess($UserId);
2438:
2439: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2440: return true;
2441: }
2442:
2443: public function AddContactsFromFile($UserId, $File)
2444: {
2445: Api::CheckAccess($UserId);
2446:
2447: $oUser = CoreModule::getInstance()->GetUserWithoutRoleCheck($UserId);
2448:
2449: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2450:
2451: if (empty($File)) {
2452: throw new ApiException(Notifications::InvalidInputParameter);
2453: }
2454:
2455: $oApiFileCache = new \Aurora\System\Managers\Filecache();
2456:
2457: $sTempFilePath = $oApiFileCache->generateFullFilePath($oUser->UUID, $File); // Temp files with access from another module should be stored in System folder
2458: $aImportResult = $this->importVcf($oUser->Id, $sTempFilePath);
2459:
2460: return $aImportResult;
2461: }
2462:
2463: /**
2464: *
2465: * @param int $UserId
2466: * @param string $UUID
2467: * @param string $FileName
2468: */
2469: public function SaveContactAsTempFile($UserId, $UUID, $FileName)
2470: {
2471: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2472:
2473: Api::CheckAccess($UserId);
2474:
2475: $mResult = false;
2476:
2477: $oContact = self::Decorator()->GetContact($UUID, $UserId);
2478: if ($oContact) {
2479: $oVCard = new \Sabre\VObject\Component\VCard();
2480: Helper::UpdateVCardFromContact($oContact, $oVCard);
2481: $sVCardData = $oVCard->serialize();
2482: if ($sVCardData) {
2483: $sUUID = \Aurora\System\Api::getUserUUIDById($UserId);
2484: $sTempName = md5($sUUID . $UUID);
2485: $oApiFileCache = new \Aurora\System\Managers\Filecache();
2486:
2487: $oApiFileCache->put($sUUID, $sTempName, $sVCardData);
2488: if ($oApiFileCache->isFileExists($sUUID, $sTempName)) {
2489: $mResult = \Aurora\System\Utils::GetClientFileResponse(
2490: null,
2491: $UserId,
2492: $FileName,
2493: $sTempName,
2494: $oApiFileCache->fileSize($sUUID, $sTempName)
2495: );
2496: }
2497: }
2498: }
2499:
2500: return $mResult;
2501: }
2502: /***** public functions might be called with web API *****/
2503:
2504: /***** private functions *****/
2505: private function importVcf($iUserId, $sTempFilePath, $sStorage = null)
2506: {
2507: $aImportResult = array(
2508: 'ParsedCount' => 0,
2509: 'ImportedCount' => 0,
2510: 'ImportedUids' => []
2511: );
2512: // You can either pass a readable stream, or a string.
2513: $oHandler = fopen($sTempFilePath, 'r');
2514: $oSplitter = new \Sabre\VObject\Splitter\VCard($oHandler, \Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES);
2515: $oContactsDecorator = Module::Decorator();
2516:
2517: $aGroupsData = [];
2518: $aContactsData = [];
2519: while ($oVCard = $oSplitter->getNext()) {
2520: set_time_limit(30);
2521:
2522: $Uid = (string) $oVCard->UID;
2523: if (empty($Uid)) {
2524: $Uid = UUIDUtil::getUUID();
2525: }
2526: if ((isset($oVCard->KIND) && (string) $oVCard->KIND === 'GROUP') ||
2527: (isset($oVCard->{'X-ADDRESSBOOKSERVER-KIND'}) && (string) $oVCard->{'X-ADDRESSBOOKSERVER-KIND'} === 'GROUP')) {
2528: $aGroupsData[] = Classes\VCard\Helper::GetGroupDataFromVcard($oVCard, $Uid);
2529: } else {
2530: $aContactData = Classes\VCard\Helper::GetContactDataFromVcard($oVCard, $Uid);
2531: $oContact = self::Decorator()->GetContact($Uid, $iUserId);
2532: $aImportResult['ParsedCount']++;
2533: if (!$oContact) {
2534: if (isset($sStorage)) {
2535: $aContactData['Storage'] = $sStorage;
2536: }
2537: $aContactsData[$Uid] = $aContactData;
2538: }
2539: }
2540: }
2541:
2542: foreach ($aContactsData as $key => $aContactData) {
2543: $CreatedContactData = $oContactsDecorator->CreateContact($aContactData, $iUserId);
2544: if ($CreatedContactData) {
2545: $aImportResult['ImportedCount']++;
2546: $aImportResult['ImportedUids'][] = $CreatedContactData['UUID'];
2547: $aContactsData[$key]['NewUUID'] = $CreatedContactData['UUID'];
2548: }
2549: }
2550:
2551: foreach ($aGroupsData as $aGroupData) {
2552: if (isset($aGroupData['Contacts'])) {
2553: $aUuids = $aGroupData['Contacts'];
2554: $aGroupData['Contacts'] = [];
2555: foreach ($aUuids as $value) {
2556: if (isset($aContactsData[$value])) {
2557: $aGroupData['Contacts'][] = $aContactsData[$value]['NewUUID'];
2558: }
2559: }
2560: }
2561: $oContactsDecorator->CreateGroup($aGroupData, $iUserId);
2562: }
2563:
2564: return $aImportResult;
2565: }
2566:
2567: protected function populateContactArguments(&$aArgs)
2568: {
2569: $mResult = false;
2570: $this->broadcastEvent('PopulateContactArguments', $aArgs, $mResult);
2571: return $mResult;
2572: }
2573:
2574: private function prepareFiltersFromStorage($UserId, $Storage = '', $AddressBookId = 0, &$Query = null, &$WhereQuery = null, $Suggestions = false)
2575: {
2576: $aArgs = [
2577: 'UserId' => $UserId,
2578: 'Storage' => $Storage,
2579: 'AddressBookId' => $AddressBookId,
2580: 'IsValid' => false,
2581: 'Query' => $Query,
2582: 'Suggestions' => $Suggestions
2583: ];
2584:
2585: $this->broadcastEvent('PrepareFiltersFromStorage', $aArgs, $WhereQuery);
2586: if (!$aArgs['IsValid']) {
2587: throw new ApiException(Notifications::InvalidInputParameter, null, 'Invalid Storage parameter value');
2588: }
2589: return $WhereQuery;
2590: }
2591:
2592: public function onAfterUseEmails($Args, &$Result)
2593: {
2594: $aAddresses = $Args['Emails'];
2595: $iUserId = $Args['IdUser'];
2596: foreach ($aAddresses as $sEmail => $sName) {
2597: try {
2598: $contactsColl = self::Decorator()->GetContactsByEmails($iUserId, StorageType::Personal, [$sEmail]);
2599:
2600: $oContact = $contactsColl->first();
2601: if (!$oContact) {
2602: $contactsColl = self::Decorator()->GetContactsByEmails($iUserId, StorageType::Collected, [$sEmail]);
2603: $oContact = $contactsColl->first();
2604: }
2605:
2606: if ($oContact) {
2607: ContactCard::where('CardId', $oContact->Id)->update(['Frequency' => $oContact->Frequency + 1]);
2608: } else {
2609: self::Decorator()->CreateContact([
2610: 'FullName' => $sName,
2611: 'PersonalEmail' => $sEmail,
2612: 'Auto' => true,
2613: 'Storage' => StorageType::Collected,
2614: ], $iUserId);
2615: }
2616: } catch (\Exception $ex) {
2617: }
2618: }
2619: }
2620:
2621: public function onGetBodyStructureParts($aParts, &$aResultParts)
2622: {
2623: foreach ($aParts as $oPart) {
2624: if ($oPart instanceof \MailSo\Imap\BodyStructure &&
2625: ($oPart->ContentType() === 'text/vcard' || $oPart->ContentType() === 'text/x-vcard')) {
2626: $aResultParts[] = $oPart;
2627: break;
2628: }
2629: }
2630: }
2631:
2632: public function onBeforeDeleteUser(&$aArgs, &$mResult)
2633: {
2634: if (isset($aArgs['UserId'])) {
2635: $this->userPublicIdToDelete = Api::getUserPublicIdById($aArgs['UserId']);
2636: }
2637: }
2638:
2639: public function onAfterDeleteUser(&$aArgs, &$mResult)
2640: {
2641: if ($mResult && $this->userPublicIdToDelete) {
2642: $abooks = Backend::Carddav()->getAddressBooksForUser(Constants::PRINCIPALS_PREFIX . $this->userPublicIdToDelete);
2643: if ($abooks) {
2644: foreach ($abooks as $book) {
2645: Backend::Carddav()->deleteAddressBook($book['id']);
2646: }
2647: }
2648: }
2649: }
2650:
2651: public function onContactToResponseArray($aArgs, &$mResult)
2652: {
2653: if (isset($aArgs[0]) && $aArgs[0] instanceof Contact && is_array($mResult)) {
2654: $mResult['UUID'] = (string) $mResult['Id']; // The UUID property type must be a string.
2655: }
2656: }
2657: /***** private functions *****/
2658:
2659: public function GetAddressBook($UserId, $UUID)
2660: {
2661: Api::CheckAccess($UserId);
2662:
2663: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2664:
2665: $principalUri = Constants::PRINCIPALS_PREFIX . \Aurora\System\Api::getUserPublicIdById($UserId);
2666:
2667: return Backend::Carddav()->getAddressBookForUser($principalUri, $UUID);
2668: }
2669:
2670: public function GetAddressBooks($UserId = null)
2671: {
2672: $aResult = [];
2673:
2674: Api::CheckAccess($UserId);
2675:
2676: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2677:
2678: return $aResult;
2679: }
2680:
2681: public function CreateAddressBook($AddressBookName, $UserId = null, $UUID = null)
2682: {
2683: $mResult = false;
2684:
2685: Api::CheckAccess($UserId);
2686:
2687: if (isset($UUID)) {
2688: $sAddressBookUUID = $UUID;
2689: } else {
2690: $sAddressBookUUID = UUIDUtil::getUUID();
2691: }
2692:
2693: $userPublicId = Api::getUserPublicIdById($UserId);
2694:
2695: $iAddressBookId = Backend::Carddav()->createAddressBook(Constants::PRINCIPALS_PREFIX . $userPublicId, $sAddressBookUUID, ['{DAV:}displayname' => $AddressBookName]);
2696:
2697: if (is_numeric($iAddressBookId)) {
2698: $oAddressBook = Backend::Carddav()->getAddressBookById($iAddressBookId);
2699: if ($oAddressBook) {
2700: return [
2701: 'Id' => StorageType::AddressBook . '-' . $oAddressBook['id'],
2702: 'EntityId' => (int) $oAddressBook['id'],
2703: 'CTag' => (int) $oAddressBook['{http://sabredav.org/ns}sync-token'],
2704: 'Display' => true,
2705: 'Owner' => basename($oAddressBook['principaluri']),
2706: 'Order' => 1,
2707: 'DisplayName' => $oAddressBook['{DAV:}displayname'],
2708: 'Uri' => $oAddressBook['uri']
2709: ];
2710:
2711: }
2712: }
2713:
2714: return $mResult;
2715: }
2716:
2717: public function UpdateAddressBook($EntityId, $AddressBookName, $UserId = null)
2718: {
2719: $mResult = false;
2720:
2721: Api::CheckAccess($UserId);
2722:
2723: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2724:
2725: if ($this->CheckAccessToAddressBook($UserId, $EntityId, Access::Write)) {
2726: $propParch = new PropPatch([
2727: '{DAV:}displayname' => $AddressBookName
2728: ]);
2729: Backend::Carddav()->updateAddressBook($EntityId, $propParch);
2730: $mResult = $propParch->commit();
2731: } else {
2732: throw new ApiException(Notifications::AccessDenied, null, 'AccessDenied');
2733: }
2734:
2735: return $mResult;
2736: }
2737:
2738: public function DeleteAddressBook($EntityId, $UserId = null)
2739: {
2740: $mResult = false;
2741:
2742: Api::CheckAccess($UserId);
2743:
2744: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2745:
2746: $userPublicId = Api::getUserPublicIdById($UserId);
2747:
2748: $abook = Capsule::connection()->table('adav_addressbooks')
2749: ->where('id', $EntityId)
2750: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
2751: ->first();
2752:
2753: if ($abook) {
2754: Backend::Carddav()->deleteAddressBook($EntityId);
2755: $mResult = true;
2756: }
2757:
2758: return $mResult;
2759: }
2760:
2761: public function DeleteUsersAddressBooks($UserId = null)
2762: {
2763: $mResult = false;
2764:
2765: Api::CheckAccess($UserId);
2766:
2767: \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
2768:
2769: $userPublicId = Api::getUserPublicIdById($UserId);
2770:
2771: $abooks = Capsule::connection()->table('adav_addressbooks')
2772: ->where('principaluri', Constants::PRINCIPALS_PREFIX . $userPublicId)
2773: ->get();
2774:
2775: foreach ($abooks as $abook) {
2776: Backend::Carddav()->deleteAddressBook($abook->id);
2777: $mResult = true;
2778: }
2779:
2780: return $mResult;
2781: }
2782:
2783: public function GetStoragesMapToAddressbooks()
2784: {
2785: return [];
2786: }
2787:
2788: protected function getGetContactsQueryBuilder($UserId, $Storage = '', $AddressBookId = null, Builder $Filters = null, $Suggestions = false, $withGroups = false)
2789: {
2790: if ($Filters instanceof Builder) {
2791: $query = & $Filters;
2792: } else {
2793: $query = ContactCard::query();
2794: }
2795:
2796: $con = Capsule::connection();
2797: $query->select(
2798: 'adav_cards.id as Id',
2799: 'adav_cards.id as UUID',
2800: 'adav_cards.uri as Uri',
2801: 'adav_cards.addressbookid as Storage',
2802: 'etag as ETag',
2803: $con->raw('FROM_UNIXTIME(lastmodified) as DateModified'),
2804: 'contacts_cards.PrimaryEmail',
2805: 'contacts_cards.PersonalEmail',
2806: 'contacts_cards.BusinessEmail',
2807: 'contacts_cards.OtherEmail',
2808: 'contacts_cards.BusinessCompany',
2809: 'contacts_cards.FullName',
2810: 'contacts_cards.FirstName',
2811: 'contacts_cards.LastName',
2812: 'contacts_cards.Frequency',
2813: 'contacts_cards.Properties',
2814: $con->raw('(Frequency/CEIL(DATEDIFF(CURDATE() + INTERVAL 1 DAY, FROM_UNIXTIME(lastmodified))/30)) as AgeScore'),
2815: 'core_users.Id as UserId'
2816: )
2817: ->join('adav_cards', 'contacts_cards.CardId', '=', 'adav_cards.id')
2818: ->where(function ($wherQuery) use ($UserId, $Storage, $AddressBookId, $query, $Suggestions) {
2819: $this->prepareFiltersFromStorage($UserId, $Storage, $AddressBookId, $query, $wherQuery, $Suggestions);
2820: });
2821: if (!$withGroups) {
2822: $query->where('IsGroup', false);
2823: }
2824: if ($Suggestions) {
2825: $query->where('Frequency', '>=', 0);
2826: }
2827:
2828: $query->leftJoin('core_users', 'adav_addressbooks.principaluri', '=', Capsule::connection()->raw("CONCAT('principals/', " . Capsule::connection()->getTablePrefix() . "core_users.PublicId)"));
2829:
2830: return $query;
2831: }
2832: }
2833: