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\CalendarMeetingsPlugin;
9:
10: use Aurora\Modules\Core\Models\User;
11: use Aurora\Modules\Mail\Models\MailAccount;
12: use Aurora\System\Api;
13:
14: /**
15: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
16: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
17: * @copyright Copyright (c) 2023, Afterlogic Corp.
18: */
19: class Manager extends \Aurora\Modules\Calendar\Manager
20: {
21: /**
22: * Processing response to event invitation. [Aurora only.](http://dev.afterlogic.com/aurora)
23: *
24: * @param string $sUserPublicId
25: * @param string $sCalendarId Calendar ID
26: * @param string $sEventId Event ID
27: * @param string $sAttendee Attendee identified by email address
28: * @param string $sAction Appointment actions. Accepted values:
29: * - "ACCEPTED"
30: * - "DECLINED"
31: * - "TENTATIVE"
32: *
33: * @return bool
34: */
35: public function updateAppointment($sUserPublicId, $sCalendarId, $sEventId, $sAttendee, $sAction)
36: {
37: $oResult = null;
38:
39: $aData = $this->oStorage->getEvent($sUserPublicId, $sCalendarId, $sEventId);
40: if ($aData !== false) {
41: $oVCal = $aData['vcal'];
42: $oVCal->METHOD = 'REQUEST';
43: return $this->appointmentAction($sUserPublicId, $sAttendee, $sAction, $sCalendarId, $oVCal->serialize());
44: }
45:
46: return $oResult;
47: }
48:
49: /**
50: * Allows for responding to event invitation (accept / decline / tentative). [Aurora only.](http://dev.afterlogic.com/aurora)
51: *
52: * @param int|string $sUserPublicId Account object
53: * @param string $sAttendee Attendee identified by email address
54: * @param string $sAction Appointment actions. Accepted values:
55: * - "ACCEPTED"
56: * - "DECLINED"
57: * - "TENTATIVE"
58: * @param string $sCalendarId Calendar ID
59: * @param string $sData ICS data of the response
60: * @param bool $bExternal If **true**, it is assumed attendee is external to the system
61: *
62: * @return bool
63: */
64: public function appointmentAction($sUserPublicId, $sAttendee, $sAction, $sCalendarId, $sData, $bExternal = false)
65: {
66: $oUser = null;
67: $oAttendeeUser = null;
68: $oDefaultUser = null;
69: $bDefaultAccountAsEmail = false;
70:
71: if (isset($sUserPublicId)) {
72: $bDefaultAccountAsEmail = false;
73: $oUser = Api::GetModuleDecorator('Core')->GetUserByPublicId($sUserPublicId);
74: $oDefaultUser = $oUser;
75: } else {
76: $oAttendeeUser = Api::GetModuleDecorator('Core')->GetUserByPublicId($sAttendee);
77: if ($oAttendeeUser instanceof User) {
78: $bDefaultAccountAsEmail = false;
79: $oDefaultUser = $oAttendeeUser;
80: } else {
81: $bDefaultAccountAsEmail = true;
82: }
83: }
84: $oFromAccount = null;
85: if ($oDefaultUser && $oDefaultUser->PublicId !== $sAttendee) {
86: $oMailModule = Api::GetModule('Mail');
87: if ($oMailModule) {
88: $aAccounts = $oMailModule->getAccountsManager()->getUserAccounts($oDefaultUser->Id);
89: foreach ($aAccounts as $oAccount) {
90: if ($oAccount instanceof MailAccount && $oAccount->Email === $sAttendee) {
91: $oFromAccount = $oAccount;
92: break;
93: }
94: }
95: }
96: }
97:
98: $bResult = false;
99: $sEventId = null;
100:
101: $sTo = $sSubject = $sBody = $sSummary = '';
102:
103: $oVCal = \Sabre\VObject\Reader::read($sData);
104: if ($oVCal) {
105: $sMethod = $sMethodOriginal = (string) $oVCal->METHOD;
106:
107: if (isset($oVCal->VEVENT) && count($oVCal->VEVENT) > 0) {
108: foreach ($oVCal->VEVENT as $oVEvent) {
109: $sEventId = (string)$oVEvent->UID;
110: if (isset($oVEvent->SUMMARY)) {
111: $sSummary = (string)$oVEvent->SUMMARY;
112: }
113: if (isset($oVEvent->ORGANIZER)) {
114: $sTo = str_replace('mailto:', '', strtolower((string)$oVEvent->ORGANIZER));
115: $sTo = str_replace('principals/', '', $sTo);
116: }
117: if (strtoupper($sMethodOriginal) === 'REQUEST') {
118: $sMethod = 'REPLY';
119: $sSubject = $sSummary;
120:
121: $sPartstat = strtoupper($sAction);
122: switch ($sPartstat) {
123: case 'ACCEPTED':
124: $sSubject = $this->GetModule()->i18N('SUBJECT_PREFFIX_ACCEPTED') . ': '. $sSubject;
125: break;
126: case 'DECLINED':
127: $sSubject = $this->GetModule()->i18N('SUBJECT_PREFFIX_DECLINED') . ': '. $sSubject;
128: break;
129: case 'TENTATIVE':
130: $sSubject = $this->GetModule()->i18N('SUBJECT_PREFFIX_TENTATIVE') . ': '. $sSubject;
131: break;
132: }
133:
134: $sCN = '';
135: if (isset($oDefaultUser) && $sAttendee === $oDefaultUser->PublicId) {
136: $sCN = !empty($oDefaultUser->Name) ? $oDefaultUser->Name : $sAttendee;
137: }
138:
139: $bFoundAteendee = false;
140: if ($oVEvent->ATTENDEE) {
141: foreach ($oVEvent->ATTENDEE as &$oAttendeeItem) {
142: $sEmail = str_replace('mailto:', '', strtolower((string)$oAttendeeItem));
143: if (strtolower($sEmail) === strtolower($sAttendee)) {
144: $oAttendeeItem['CN'] = $sCN;
145: $oAttendeeItem['PARTSTAT'] = $sPartstat;
146: $oAttendeeItem['RESPONDED-AT'] = gmdate("Ymd\THis\Z");
147:
148: $bFoundAteendee = true;
149: }
150: }
151: }
152: }
153:
154: $oVEvent->{'LAST-MODIFIED'} = new \DateTime('now', new \DateTimeZone('UTC'));
155:
156: if (!$bFoundAteendee) {
157: unset($oVEvent);
158: }
159: }
160: $oVCal->METHOD = $sMethod;
161: $sBody = $oVCal->serialize();
162:
163: if ($sUserPublicId === null) {
164: $oCalendar = $this->getDefaultCalendar($oDefaultUser->PublicId);
165: if ($oCalendar) {
166: $sCalendarId = $oCalendar->Id;
167: }
168: }
169:
170: if ($sCalendarId !== false && $bExternal === false && !$bDefaultAccountAsEmail) {
171: unset($oVCal->METHOD);
172: if (isset($oDefaultUser)) {
173: if (strtoupper($sAction) == 'DECLINED' || strtoupper($sMethod) == 'CANCEL') {
174: $this->deleteEvent($sAttendee, $sCalendarId, $sEventId);
175: } else {
176: $this->oStorage->updateEventRaw(
177: $oDefaultUser->PublicId,
178: $sCalendarId,
179: $sEventId,
180: $oVCal->serialize()
181: );
182: }
183: }
184: }
185:
186: if (strtoupper($sMethodOriginal) == 'REQUEST') {
187: if (empty($sTo)) {
188: throw new \Aurora\Modules\CalendarMeetingsPlugin\Exceptions\Exception(
189: \Aurora\Modules\CalendarMeetingsPlugin\Enums\ErrorCodes::CannotSendAppointmentMessageNoOrganizer
190: );
191: } elseif (!empty($sBody) && isset($oDefaultUser) && $oDefaultUser instanceof User) {
192: $bResult = \Aurora\Modules\CalendarMeetingsPlugin\Classes\Helper::sendAppointmentMessage(
193: $oDefaultUser->PublicId,
194: $sTo,
195: $sSubject,
196: $sBody,
197: $sMethod,
198: '',
199: $oFromAccount,
200: $sAttendee
201: );
202: } else {
203: throw new \Aurora\Modules\CalendarMeetingsPlugin\Exceptions\Exception(
204: \Aurora\Modules\CalendarMeetingsPlugin\Enums\ErrorCodes::CannotSendAppointmentMessage
205: );
206: }
207: } else {
208: $bResult = true;
209: }
210: }
211: }
212:
213: if (!$bResult) {
214: Api::Log('Ics Appointment Action FALSE result!', \Aurora\System\Enums\LogLevel::Error);
215: if ($sUserPublicId) {
216: Api::Log('Email: ' . $oDefaultUser->PublicId . ', Action: '. $sAction.', Data:', \Aurora\System\Enums\LogLevel::Error);
217: }
218: Api::Log($sData, \Aurora\System\Enums\LogLevel::Error);
219: } else {
220: $bResult = $sEventId;
221: }
222:
223: return $bResult;
224: }
225: }
226: