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 Aurora\Modules\Contacts\Classes\CTag;
11: use Aurora\Modules\Contacts\Enums\SortField;
12: use Aurora\Modules\Contacts\Enums\StorageType;
13: use Aurora\Modules\Contacts\Models\Group;
14: use Aurora\Modules\Contacts\Models\Contact;
15: use Illuminate\Database\Eloquent\Builder;
16: use Illuminate\Database\Capsule\Manager as Capsule;
17:
18: /**
19: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
20: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
21: * @copyright Copyright (c) 2023, Afterlogic Corp.
22: *
23: * @ignore
24: */
25: class Manager extends \Aurora\System\Managers\AbstractManager
26: {
27: /**
28: * @param \Aurora\System\Module\AbstractModule $oModule
29: */
30: public function __construct(\Aurora\System\Module\AbstractModule $oModule = null)
31: {
32: parent::__construct($oModule);
33: }
34:
35: /**
36: *
37: * @param string $sUUID
38: * @return \Aurora\Modules\Contacts\Models\Contact
39: */
40: public function getContact($sUUID)
41: {
42: return Contact::where('UUID', $sUUID)->first();
43: }
44:
45: /**
46: *
47: * @param string $sEmail
48: * @return \Aurora\Modules\Contacts\Classes\Contact
49: */
50: public function getContactByEmail($iUserId, $sEmail)
51: {
52: return Contact::where('IdUser', $iUserId)
53: ->where('ViewEmail', $sEmail)
54: ->orderBy('FullName')
55: ->first();
56: }
57:
58: /**
59: * Returns group item identified by its ID.
60: *
61: * @param string $sUUID Group ID
62: *
63: * @return \Aurora\Modules\Contacts\Models\Group
64: */
65: public function getGroup($sUUID)
66: {
67: return Group::firstWhere('UUID', $sUUID);
68: }
69:
70: /**
71: * Returns group item identified by its name.
72: *
73: * @param string $sName Group name
74: *
75: * @return \Aurora\Modules\Contacts\Classes\Group
76: */
77: public function getGroupByName($sName, $iUserId)
78: {
79: return Group::where('Name', $sName)->where('IdUser', $iUserId)->first();
80: }
81:
82: /**
83: * Updates contact information. Using this method is required to finalize changes made to the contact object.
84: *
85: * @param \Aurora\Modules\Contacts\Models\Contact $oContact Contact object to be updated
86: * @param bool $bUpdateFromGlobal
87: *
88: * @return bool
89: */
90: public function updateContact($oContact)
91: {
92: $oContact->DateModified = date('Y-m-d H:i:s');
93: $oContact->calculateETag();
94: $res = $oContact->save();
95: if ($res) {
96: if ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
97: $this->updateCTag($oContact->IdUser, $oContact->getStorageWithId());
98: } else {
99: $this->updateCTag($oContact->IdTenant, $oContact->getStorageWithId());
100: }
101: }
102:
103: return $res;
104: }
105:
106: /**
107: * Updates group information. Using this method is required to finalize changes made to the group object.
108: *
109: * @param \Aurora\Modules\Contacts\Classes\Group $oGroup
110: *
111: * @return bool
112: */
113: public function updateGroup($oGroup)
114: {
115: $res = false;
116: if ($oGroup instanceof Models\Group) {
117: $res = $oGroup->save();
118: if ($res) {
119: $this->updateCTag($oGroup->IdUser, 'personal');
120: // foreach ($oGroup->GroupContacts as $oGroupContact)
121: // {
122: // $oGroupContact->GroupUUID = $oGroup->UUID;
123: // $res = $oGroupContact->save();
124: // }
125: }
126: }
127:
128: return $res;
129: }
130:
131: /**
132: * Returns list of contacts which match the specified criteria
133: *
134: * @param int $iUserId User ID
135: * @param string $sSearch Search pattern. Default value is empty string.
136: * @param string $sFirstCharacter If specified, will only return contacts with names starting from the specified character. Default value is empty string.
137: * @param string $sGroupUUID. Default value is **''**.
138: * @param int $iTenantId Group ID. Default value is null.
139: * @param bool $bAll Default value is null
140: *
141: * @return int
142: */
143: public function getContactsCount($Filters = null, $sGroupUUID = '')
144: {
145: $oQuery = ($Filters instanceof Builder) ? $Filters : Contact::query();
146: if (!empty($sGroupUUID)) {
147: $oGroup = Group::firstWhere('UUID', $sGroupUUID);
148: if ($oGroup) {
149: $oQuery->whereHas('Groups', function ($oSubQuery) use ($oGroup) {
150: return $oSubQuery->where('Groups.Id', $oGroup->Id);
151: });
152: }
153: }
154: return $oQuery->count();
155: }
156:
157: /**
158: * Returns list of contacts within specified range, sorted according to specified requirements.
159: *
160: * @param int $iSortField Sort field. Accepted values:
161: *
162: * \Aurora\Modules\Contacts\Enums\SortField::Name
163: * \Aurora\Modules\Contacts\Enums\SortField::Email
164: * \Aurora\Modules\Contacts\Enums\SortField::Frequency
165: *
166: * Default value is **\Aurora\Modules\Contacts\Enums\SortField::Email**.
167: * @param int $iSortOrder Sorting order. Accepted values:
168: *
169: * \Aurora\System\Enums\SortOrder::ASC
170: * \Aurora\System\Enums\SortOrder::DESC,
171: *
172: * for ascending and descending respectively. Default value is **\Aurora\System\Enums\SortOrder::ASC**.
173: * @param int $iOffset Ordinal number of the contact item the list stars with. Default value is **0**.
174: * @param int $iLimit The upper limit for total number of contacts returned. Default value is **20**.
175: * @param Builder $oFilters
176: * @param array $aViewAttrs
177: *
178: * @return array|bool
179: */
180: public function getContacts(
181: $iSortField = SortField::Name,
182: $iSortOrder = \Aurora\System\Enums\SortOrder::ASC,
183: $iOffset = 0,
184: $iLimit = 20,
185: $oFilters = null,
186: $aViewAttrs = array()
187: )
188: {
189: $sSortField = 'FullName';
190: switch ($iSortField) {
191: case SortField::Email:
192: $sSortField = 'ViewEmail';
193: break;
194: case SortField::Frequency:
195: $sSortField = 'AgeScore';
196: $oFilters->select(Capsule::connection()->raw('*, (Frequency/CEIL(DATEDIFF(CURDATE() + INTERVAL 1 DAY, DateModified)/30)) as AgeScore'));
197: break;
198: case SortField::FirstName:
199: $sSortField = 'FirstName';
200: break;
201: case SortField::LastName:
202: $sSortField = 'LastName';
203: break;
204: case SortField::Name:
205: $sSortField = 'FullName';
206: break;
207: }
208: if ($iOffset > 0) {
209: $oFilters->offset($iOffset);
210: }
211: if ($iLimit > 0) {
212: $oFilters->limit($iLimit);
213: }
214:
215: return $oFilters
216: ->orderBy($sSortField, $iSortOrder === \Aurora\System\Enums\SortOrder::ASC ? 'asc' : 'desc')
217: ->get();
218: }
219:
220: /**
221: * Returns list of contacts within specified range, sorted according to specified requirements.
222: *
223: * @param int $iSortField Sort field. Accepted values:
224: *
225: * \Aurora\Modules\Contacts\Enums\SortField::Name
226: * \Aurora\Modules\Contacts\Enums\SortField::Email
227: * \Aurora\Modules\Contacts\Enums\SortField::Frequency
228: *
229: * Default value is **\Aurora\Modules\Contacts\Enums\SortField::Email**.
230: * @param int $iSortOrder Sorting order. Accepted values:
231: *
232: * \Aurora\System\Enums\SortOrder::ASC
233: * \Aurora\System\Enums\SortOrder::DESC,
234: *
235: * for ascending and descending respectively. Default value is **\Aurora\System\Enums\SortOrder::ASC**.
236: * @param int $iOffset Ordinal number of the contact item the list stars with. Default value is **0**.
237: * @param int $iLimit The upper limit for total number of contacts returned. Default value is **20**.
238: * @param Builder $oFilters
239: * @param array $aViewAttrs
240: *
241: * @return array|bool
242: */
243: public function getContactsAsArray(
244: $iSortField = SortField::Name,
245: $iSortOrder = \Aurora\System\Enums\SortOrder::ASC,
246: $iOffset = 0,
247: $iLimit = 20,
248: $oFilters = null,
249: $aViewAttrs = array()
250: )
251: {
252: return $this->getContacts($iSortField, $iSortOrder, $iOffset, $iLimit, $oFilters, $aViewAttrs)->toArray();
253: }
254:
255: /**
256: * Returns uid list of contacts.
257:
258: * @param Builder $oFilters
259:
260: *
261: * @return array|bool
262: */
263: public function getContactUids(Builder $oFilters)
264: {
265: return $oFilters->get()->map(
266: function ($oContact) {
267: return $oContact->UUID;
268: }
269: )->toArray();
270: }
271:
272: /**
273: * Returns list of user's groups.
274: *
275: * @param int $iUserId User ID
276: *
277: * @return array|bool
278: */
279: public function getGroups($iUserId, $oFilters = null)
280: {
281: $oQuery = $oFilters instanceof Builder ? $oFilters : Group::query();
282: return $oQuery->where('IdUser', $iUserId)->orderBy('Name')->get();
283: }
284:
285: /**
286: * The method is used for saving created contact to the database.
287: *
288: * @param \Aurora\Modules\Contacts\Models\Contact $oContact
289: *
290: * @return bool
291: */
292: public function createContact($oContact)
293: {
294: $res = false;
295:
296: $oQuery = Contact::where('IdUser', $oContact->IdUser)
297: ->where('UUID', $oContact->UUID);
298:
299: if (!$oContact->Storage === StorageType::AddressBook) {
300: $oQuery = $oQuery->where('AddressBookId', $oContact->AddressBookId);
301: }
302:
303: if (!$oQuery->exists()) {
304: $oContact->DateModified = date('Y-m-d H:i:s');
305: $oContact->calculateETag();
306: $res = $oContact->save();
307:
308: if ($res) {
309: if ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
310: $this->updateCTag($oContact->IdUser, $oContact->getStorageWithId());
311: } else {
312: $this->updateCTag($oContact->IdTenant, $oContact->getStorageWithId());
313: }
314:
315: // foreach ($oContact->GroupsContacts as $oGroupContact)
316: // {
317: // $oGroupContact->ContactUUID = $oContact->UUID;
318: // $oGroupContact->save();
319: // }
320: }
321: }
322:
323: return $res;
324: }
325:
326: /**
327: * The method is used for saving created group to the database.
328: *
329: * @param \Aurora\Modules\Contacts\Models\Group $oGroup
330: *
331: * @return bool
332: */
333: public function createGroup($oGroup)
334: {
335: return $oGroup->save();
336: }
337:
338: /**
339: * Deletes one or multiple contacts from address book.
340: *
341: * @param array $aContactUUIDs Array of strings
342: *
343: * @return bool
344: */
345: public function deleteContacts($iIdUser, $sStorage, $aContactUUIDs)
346: {
347: $mResult = !!Contact::whereIn('UUID', $aContactUUIDs)->delete();
348:
349: if ($mResult) {
350: $oUser = \Aurora\Modules\Core\Module::getInstance()->GetUserUnchecked($iIdUser);
351: if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
352: $iIdUser = $sStorage === StorageType::Personal || $sStorage === StorageType::AddressBook ? $oUser->Id : $oUser->IdTenant;
353: }
354: // $this->updateCTag($iIdUser, $sStorage);
355: }
356: return $mResult;
357: }
358:
359: /**
360: * Deletes specific groups from address book.
361: *
362: * @param array $aGroupUUIDs array of strings - groups identifiers.
363: *
364: * @return bool
365: */
366: public function deleteGroups($aGroupUUIDs)
367: {
368: $oQuery = Group::whereIn('UUID', $aGroupUUIDs);
369: $aGroups = $oQuery->get();
370: foreach ($aGroups as $oGroup) {
371: foreach ($oGroup->Contacts as $oContact) {
372: if ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
373: $this->updateCTag($oContact->IdUser, $oContact->getStorageWithId());
374: } else {
375: $this->updateCTag($oContact->IdTenant, $oContact->getStorageWithId());
376: }
377: $oContact->DateModified = date('Y-m-d H:i:s');
378: $oContact->calculateETag();
379: $oContact->save();
380: }
381: }
382: return !!$oQuery->delete();
383: }
384:
385: public function deleteGroupsByUserId($iUserId)
386: {
387: return !!Group::where('IdUser', $iUserId)->delete();
388: }
389:
390: /**
391: * Adds one or multiple contacts to the specific group.
392: *
393: * @param string $sGroupUUID Group identifier to be used
394: * @param array $aContactUUIDs Array of integers
395: *
396: * @return bool
397: */
398: public function addContactsToGroup($sGroupUUID, $aContactUUIDs)
399: {
400: $res = true;
401:
402: $oGroup = $this->getGroup($sGroupUUID);
403: $aContacts = Contact::whereIn('UUID', $aContactUUIDs)->get();
404: $oGroup->Contacts()->sync(
405: $oGroup->Contacts->merge(
406: $aContacts
407: )->map(function ($oContact) {
408: return $oContact->Id;
409: })
410: );
411: $aContacts->each(function ($oContact) {
412: if ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
413: $this->updateCTag($oContact->IdUser, $oContact->getStorageWithId());
414: } else {
415: $this->updateCTag($oContact->IdTenant, $oContact->getStorageWithId());
416: }
417:
418: $oContact->DateModified = date('Y-m-d H:i:s');
419: $oContact->calculateETag();
420: $oContact->save();
421: });
422:
423: return $res;
424: }
425:
426: /**
427: * The method deletes one or multiple contacts from the group.
428: *
429: * @param string $sGroupUUID Group identifier
430: * @param array $aContactUUIDs Array of integers
431: *
432: * @return bool
433: */
434: public function removeContactsFromGroup($sGroupUUID, $aContactUUIDs)
435: {
436: $oGroup = $this->getGroup($sGroupUUID);
437: $aContacts = Contact::whereIn('UUID', $aContactUUIDs)->get();
438: $aContactIds = $aContacts->map(function ($oContact) {
439: return $oContact->Id;
440: });
441: $oGroup->Contacts()->detach($aContactIds);
442: $aContacts->each(function ($oContact) {
443: if ($oContact->Storage === StorageType::Personal || $oContact->Storage === StorageType::AddressBook) {
444: $this->updateCTag($oContact->IdUser, $oContact->getStorageWithId());
445: } else {
446: $this->updateCTag($oContact->IdTenant, $oContact->getStorageWithId());
447: }
448:
449: $oContact->DateModified = date('Y-m-d H:i:s');
450: $oContact->calculateETag();
451: $oContact->save();
452: });
453:
454: return true;
455: }
456:
457: public function getCTags($iUserId, $Storage)
458: {
459: return Models\CTag::where([
460: ['UserId', '=', $iUserId],
461: ['Storage', '=', $Storage]
462: ])->get();
463: }
464:
465:
466: public function getCTag($iUserId, $Storage)
467: {
468: return Models\CTag::firstWhere([
469: ['UserId', '=', $iUserId],
470: ['Storage', '=', $Storage]
471: ]);
472: }
473:
474: public function updateCTag($iUserId, $Storage)
475: {
476: $oCTag = $this->getCTag($iUserId, $Storage);
477: if ($oCTag instanceof Models\CTag) {
478: $oCTag->increment('CTag');
479: } else {
480: $oCTag = new Models\CTag();
481: $oCTag->UserId = $iUserId;
482: $oCTag->Storage = $Storage;
483: $oCTag->CTag = 1;
484: $oCTag->save();
485: }
486: }
487:
488: public function deleteContactsByUserId($iUserId, $Storage = null)
489: {
490: $aFilter = [
491: ['IdUser', '=', $iUserId]
492: ];
493: if (isset($Storage)) {
494: $aFilter[] = ['Storage', '=', $Storage];
495: }
496: Models\Contact::where($aFilter)->delete();
497: }
498:
499: public function deleteCTagsByUserId($iUserId, $Storage = null)
500: {
501: $aFilter = [
502: ['UserId', '=', $iUserId]
503: ];
504: if (isset($Storage)) {
505: $aFilter[] = ['Storage', '=', $Storage];
506: }
507: Models\CTag::where($aFilter)->delete();
508: }
509: }
510: