1: <?php
2:
3: namespace Aurora\Modules\Calendar;
4:
5: require_once dirname(__file__)."/../../system/autoload.php";
6: \Aurora\System\Api::Init();
7:
8: class Reminder
9: {
10: private $oApiUsersManager;
11:
12: private $oApiCalendarManager;
13:
14: private $oApiMailManager;
15:
16: private $oApiAccountsManager;
17:
18: private $oCalendarModule;
19:
20: /**
21: * @var array
22: */
23: private $aUsers;
24:
25: /**
26: * @var array
27: */
28: private $aCalendars;
29:
30: /**
31: * @var string
32: */
33: private $sCurRunFilePath;
34:
35: public function __construct()
36: {
37: $this->aUsers = array();
38: $this->aCalendars = array();
39: $this->sCurRunFilePath = \Aurora\System\Api::DataPath().'/reminder-run';
40:
41: $oMailModule = \Aurora\Modules\Mail\Module::getInstance();
42: $this->oCalendarModule = \Aurora\Modules\Calendar\Module::getInstance();
43:
44: $this->oApiUsersManager = \Aurora\Modules\Core\Module::getInstance()->getUsersManager() ;
45: $this->oApiCalendarManager = $this->oCalendarModule->getManager();
46: $this->oApiMailManager = $oMailModule->getMailManager();
47: $this->oApiAccountsManager = $oMailModule->getAccountsManager();
48: }
49:
50: public static function NewInstance()
51: {
52: return new self();
53: }
54:
55: /**
56: * @param string $sKey
57: * @param Aurora\Modules\Core\Classes\User $oUser = null
58: * @param array $aParams = null
59: *
60: * @return string
61: */
62: private function i18n($sKey, $oUser = null, $aParams = null, $iMinutes = null)
63: {
64: return $this->oCalendarModule->I18N($sKey, $aParams, $iMinutes, $oUser->UUID);
65: }
66:
67: /**
68: * @param string $sLogin
69: *
70: * @return CAccount
71: */
72: private function &getUser($sLogin)
73: {
74: $mResult = null;
75:
76: if (!isset($this->aUsers[$sLogin])) {
77: $this->aUsers[$sLogin] = $this->oApiUsersManager->getUserByPublicId($sLogin);
78: }
79:
80: $mResult =& $this->aUsers[$sLogin];
81:
82: if (is_array($this->aUsers[$sLogin]) && 30 < count($this->aUsers[$sLogin])) {
83: $this->aUsers = array_slice($this->aUsers, -30);
84: }
85:
86: return $mResult;
87: }
88:
89: /**
90: * @param Aurora\Modules\Core\Classes\User $oUser
91: * @param string $sUri
92: *
93: * @return CalendarInfo|null
94: */
95: private function &getCalendar($oUser, $sUri)
96: {
97: $mResult = null;
98: if ($this->oApiCalendarManager) {
99: if (!isset($this->aCalendars[$sUri])) {
100: $this->aCalendars[$sUri] = $this->oApiCalendarManager->getCalendar($oUser->PublicId, $sUri);
101: }
102:
103: if (isset($this->aCalendars[$sUri])) {
104: $mResult =& $this->aCalendars[$sUri];
105: }
106: }
107:
108: return $mResult;
109: }
110:
111: /**
112: * @param Aurora\Modules\Core\Classes\User $oUser
113: * @param string $sEventName
114: * @param string $sDateStr
115: * @param string $sCalendarName
116: * @param string $sEventText
117: * @param string $sCalendarColor
118: *
119: * @return string
120: */
121: private function createBodyHtml($oUser, $sEventName, $sDateStr, $sCalendarName, $sEventText, $sCalendarColor)
122: {
123: $sEventText = nl2br($sEventText);
124:
125: return sprintf(
126: '
127: <div style="padding: 10px; font-size: 12px; text-align: center; word-wrap: break-word;">
128: <div style="border: 4px solid %s; padding: 15px; width: 370px;">
129: <h2 style="margin: 5px; font-size: 18px; line-height: 1.4;">%s</h2>
130: <span>%s%s</span><br/>
131: <span>%s: %s</span><br/><br/>
132: <span>%s</span><br/>
133: </div>
134: <p style="color:#667766; width: 400px; font-size: 10px;">%s</p>
135: </div>',
136: $sCalendarColor,
137: $sEventName,
138: ucfirst($this->i18n('EVENT_BEGIN', $oUser)),
139: $sDateStr,
140: $this->i18n('CALENDAR', $oUser),
141: $sCalendarName,
142: $sEventText,
143: $this->i18n('EMAIL_EXPLANATION', $oUser, array(
144: 'EMAIL' => '<a href="mailto:'.$oUser->PublicId.'">'.$oUser->PublicId.'</a>',
145: 'CALENDAR_NAME' => $sCalendarName
146: ))
147: );
148: }
149:
150: /**
151: * @param Aurora\Modules\Core\Classes\User $oUser
152: * @param string $sEventName
153: * @param string $sDateStr
154: * @param string $sCalendarName
155: * @param string $sEventText
156: *
157: * @return string
158: */
159: private function createBodyText($oUser, $sEventName, $sDateStr, $sCalendarName, $sEventText)
160: {
161: return sprintf(
162: "%s\r\n\r\n%s%s\r\n\r\n%s: %s %s\r\n\r\n%s",
163: $sEventName,
164: ucfirst($this->i18n('EVENT_BEGIN', $oUser)),
165: $sDateStr,
166: $this->i18n('CALENDAR', $oUser),
167: $sCalendarName,
168: $sEventText,
169: $this->i18n('EMAIL_EXPLANATION', $oUser, array(
170: 'EMAIL' => '<a href="mailto:'.$oUser->PublicId.'">'.$oUser->PublicId.'</a>',
171: 'CALENDAR_NAME' => $sCalendarName
172: ))
173: );
174: }
175:
176: /**
177: * @param Aurora\Modules\Core\Classes\User $oUser
178: * @param string $sSubject
179: * @param string $mHtml = null
180: * @param string $mText = null
181: *
182: * @return \MailSo\Mime\Message
183: */
184: private function createMessage($oUser, $sSubject, $mHtml = null, $mText = null)
185: {
186: $oMessage = \MailSo\Mime\Message::NewInstance();
187: $oMessage->RegenerateMessageId();
188:
189: // $sXMailer = \Aurora\System\Api::GetConf('webmail.xmailer-value', '');
190: // if (0 < strlen($sXMailer))
191: // {
192: // $oMessage->SetXMailer($sXMailer);
193: // }
194:
195: $oMessage
196: ->SetFrom(\MailSo\Mime\Email::NewInstance($oUser->PublicId))
197: ->SetSubject($sSubject)
198: ;
199:
200: $oToEmails = \MailSo\Mime\EmailCollection::NewInstance($oUser->PublicId);
201: if ($oToEmails && $oToEmails->Count()) {
202: $oMessage->SetTo($oToEmails);
203: }
204:
205: if ($mHtml !== null) {
206: $oMessage->AddText($mHtml, true);
207: }
208:
209: if ($mText !== null) {
210: $oMessage->AddText($mText, false);
211: }
212:
213: return $oMessage;
214: }
215:
216: /**
217: *
218: * @param Aurora\Modules\Core\Classes\User $oUser
219: * @param string $sSubject
220: * @param string $sEventName
221: * @param string $sDate
222: * @param string $sCalendarName
223: * @param string $sEventText
224: * @param string $sCalendarColor
225: *
226: * @return bool
227: */
228: private function sendMessage($oUser, $sSubject, $sEventName, $sDate, $sCalendarName, $sEventText, $sCalendarColor)
229: {
230: $oMessage = $this->createMessage(
231: $oUser,
232: $sSubject,
233: $this->createBodyHtml($oUser, $sEventName, $sDate, $sCalendarName, $sEventText, $sCalendarColor),
234: $this->createBodyText($oUser, $sEventName, $sDate, $sCalendarName, $sEventText)
235: );
236:
237: try {
238: $oAccount = $this->oApiAccountsManager->getAccountUsedToAuthorize($oUser->PublicId);
239: if (!$oAccount instanceof \Aurora\Modules\Mail\Models\MailAccount) {
240: return false;
241: }
242: return $this->oApiMailManager->sendMessage($oAccount, $oMessage);
243: } catch (\Exception $oException) {
244: \Aurora\System\Api::Log('MessageSend Exception', \Aurora\System\Enums\LogLevel::Error, 'cron-');
245: \Aurora\System\Api::LogException($oException, \Aurora\System\Enums\LogLevel::Error, 'cron-');
246: }
247:
248: return false;
249: }
250:
251: private function getSubject($oUser, $sEventStart, $iEventStartTS, $sEventName, $sDate, $iNowTS, $bAllDay = false)
252: {
253: $sSubject = '';
254:
255: if ($bAllDay) {
256: $oEventStart = new \DateTime("@$iEventStartTS", new \DateTimeZone('UTC'));
257: $oEventStart->setTimezone(new \DateTimeZone($oUser->DefaultTimeZone ?: 'UTC'));
258: $iEventStartTS = $oEventStart->getTimestamp() - $oEventStart->getOffset();
259: }
260:
261: $iMinutes = round(($iEventStartTS - $iNowTS) / 60);
262:
263: if ($iMinutes > 0 && $iMinutes < 60) {
264: $sSubject = $this->i18n('SUBJECT_MINUTES_PLURAL', $oUser, array(
265: 'EVENT_NAME' => $sEventName,
266: 'DATE' => date('G:i', strtotime($sEventStart)),
267: 'COUNT' => $iMinutes
268: ), $iMinutes);
269: } elseif ($iMinutes >= 60 && $iMinutes < 1440) {
270: $sSubject = $this->i18n('SUBJECT_HOURS_PLURAL', $oUser, array(
271: 'EVENT_NAME' => $sEventName,
272: 'DATE' => date('G:i', strtotime($sEventStart)),
273: 'COUNT' => round($iMinutes / 60)
274: ), round($iMinutes / 60));
275: } elseif ($iMinutes >= 1440 && $iMinutes < 10080) {
276: $sSubject = $this->i18n('SUBJECT_DAYS_PLURAL', $oUser, array(
277: 'EVENT_NAME' => $sEventName,
278: 'DATE' => $sDate,
279: 'COUNT' => round($iMinutes / 1440)
280: ), round($iMinutes / 1440));
281: } elseif ($iMinutes >= 10080) {
282: $sSubject = $this->i18n('SUBJECT_WEEKS_PLURAL', $oUser, array(
283: 'EVENT_NAME' => $sEventName,
284: 'DATE' => $sDate,
285: 'COUNT' => round($iMinutes / 10080)
286: ), round($iMinutes / 10080));
287: } else {
288: $sSubject = $this->i18n('SUBJECT', $oUser, array(
289: 'EVENT_NAME' => $sEventName,
290: 'DATE' => $sDate
291: ));
292: }
293:
294: return $sSubject;
295: }
296:
297: private function getDateTimeFormat($oUser)
298: {
299: $sDateFormat = 'm/d/Y';
300: $sTimeFormat = 'h:i A';
301:
302: if ($oUser->DateFormat === \Aurora\System\Enums\DateFormat::DDMMYYYY) {
303: $sDateFormat = 'd/m/Y';
304: } elseif ($oUser->DateFormat === \Aurora\System\Enums\DateFormat::MMDDYYYY) {
305: $sDateFormat = 'm/d/Y';
306: } elseif ($oUser->DateFormat === \Aurora\System\Enums\DateFormat::DD_MONTH_YYYY) {
307: $sDateFormat = 'd m Y';
308: } elseif ($oUser->DateFormat === \Aurora\System\Enums\DateFormat::MMDDYY) {
309: $sDateFormat = 'm/d/y';
310: } elseif ($oUser->DateFormat === \Aurora\System\Enums\DateFormat::DDMMYY) {
311: $sDateFormat = 'd/m/Y';
312: }
313:
314: if ($oUser->TimeFormat == \Aurora\System\Enums\TimeFormat::F24) {
315: $sTimeFormat = 'H:i';
316: }
317:
318: return $sDateFormat.' '.$sTimeFormat;
319: }
320:
321: public function GetReminders($iStart, $iEnd)
322: {
323: $aReminders = $this->oApiCalendarManager->getReminders($iStart, $iEnd);
324: $aEvents = array();
325:
326: if ($aReminders && is_array($aReminders) && count($aReminders) > 0) {
327: $aCacheEvents = array();
328: foreach ($aReminders as $aReminder) {
329: $oUser = $this->getUser($aReminder['user']);
330:
331: $sCalendarUri = $aReminder['calendaruri'];
332: $sEventId = $aReminder['eventid'];
333: $iStartTime = $aReminder['starttime'];
334: $iReminderTime = $aReminder['time'];
335:
336: if (!isset($aCacheEvents[$sEventId]) && isset($oUser)) {
337: $aCacheEvents[$sEventId]['data'] = $this->oApiCalendarManager->getEvent($oUser->PublicId, $sCalendarUri, $sEventId);
338:
339: $dt = new \DateTime();
340: $dt->setTimestamp($iStartTime);
341: $oDefaultTimeZone = new \DateTimeZone($oUser->DefaultTimeZone ?: 'UTC');
342: $dt->setTimezone($oDefaultTimeZone);
343:
344: $aEventClear = [];
345: if (is_array($aCacheEvents[$sEventId]['data'])) {
346: $CurrentEvent = null;
347: foreach ($aCacheEvents[$sEventId]['data'] as $key =>$aEvent) {
348: if (is_int($key)) {
349: if (empty($CurrentEvent)) {
350: $CurrentEvent = $aEvent;
351: } elseif (isset($aEvent['excluded']) && $this->EventHasReminder($aEvent, $iReminderTime)) {
352: $CurrentEvent = $aEvent;
353: }
354: unset($aCacheEvents[$sEventId]['data'][$key]);
355: }
356: }
357: if (!empty($CurrentEvent)) {
358: $aCacheEvents[$sEventId]['data'][0] = $CurrentEvent;
359: }
360: }
361: $aCacheEvents[$sEventId]['time'] = $dt->format($this->getDateTimeFormat($oUser));
362: }
363:
364: if (isset($aCacheEvents[$sEventId])) {
365: $aEvents[$aReminder['user']][$sCalendarUri][$sEventId] = $aCacheEvents[$sEventId];
366: }
367: }
368: }
369: return $aEvents;
370: }
371:
372: public function EventHasReminder($aEvent, $iReminderTime)
373: {
374: foreach ($aEvent['alarms'] as $iAlarm) {
375: if ($aEvent['startTS'] - $iAlarm * 60 === (int) $iReminderTime) {
376: return true;
377: }
378: }
379: return false;
380: }
381:
382: public function Execute()
383: {
384: \Aurora\System\Api::Log('---------- Start cron script', \Aurora\System\Enums\LogLevel::Full, 'cron-');
385:
386: $oTimeZoneUTC = new \DateTimeZone('UTC');
387: $oNowDT_UTC = new \DateTimeImmutable('now', $oTimeZoneUTC);
388: $iNowTS = $oNowDT_UTC->getTimestamp();
389:
390: $oStartDT_UTC = clone $oNowDT_UTC;
391: $oStartDT_UTC = $oStartDT_UTC->sub(new \DateInterval('PT30M'));
392:
393: if (file_exists($this->sCurRunFilePath)) {
394: $handle = fopen($this->sCurRunFilePath, 'r');
395: $sCurRunFileTS = fread($handle, 10);
396: if (!empty($sCurRunFileTS) && is_numeric($sCurRunFileTS)) {
397: $oStartDT_UTC = new \DateTimeImmutable("@$sCurRunFileTS");
398: }
399: }
400:
401: $iStartTS = $oStartDT_UTC->getTimestamp();
402:
403: if ($iNowTS >= $iStartTS) {
404: \Aurora\System\Api::Log('Start time: '.$oStartDT_UTC->format('r'), \Aurora\System\Enums\LogLevel::Full, 'cron-');
405: \Aurora\System\Api::Log('End time: '.$oNowDT_UTC->format('r'), \Aurora\System\Enums\LogLevel::Full, 'cron-');
406:
407: $aEvents = $this->GetReminders($iStartTS, $iNowTS);
408:
409: foreach ($aEvents as $sEmail => $aUserCalendars) {
410: foreach ($aUserCalendars as $sCalendarUri => $aUserEvents) {
411: foreach ($aUserEvents as $aUserEvent) {
412: $aSubEvents = $aUserEvent['data'];
413:
414: if (isset($aSubEvents, $aSubEvents['vcal'])) {
415: $vCal = $aSubEvents['vcal'];
416: foreach ($aSubEvents as $mKey => $aEvent) {
417: if ($mKey !== 'vcal') {
418: $oUser = $this->getUser($sEmail);
419: $oCalendar = $this->getCalendar($oUser, $sCalendarUri);
420:
421: if ($oCalendar) {
422: $sEventId = $aEvent['uid'];
423: $sEventStart = $aEvent['start'];
424: $iEventStartTS = $aEvent['startTS'];
425: $sEventName = $aEvent['subject'];
426: $sEventText = $aEvent['description'];
427: $bAllDay = $aEvent['allDay'];
428: $sDate = $aUserEvent['time'];
429:
430: if (isset($vCal->getBaseComponent('VEVENT')->RRULE) && $iEventStartTS < $iNowTS) { // the correct date for repeatable events
431: $aBaseEvents = $vCal->getBaseComponents('VEVENT');
432: if (isset($aBaseEvents[0])) {
433: $oEventStartDT = \Aurora\Modules\Calendar\Classes\Helper::getNextRepeat($oNowDT_UTC, $aBaseEvents[0]);
434: if (isset($oEventStartDT)) {
435: $sEventStart = $oEventStartDT->format('Y-m-d H:i:s');
436: if ($bAllDay) {
437: $sDate = $oEventStartDT->format('d m Y');
438: } else {
439: $sDate = $oEventStartDT->format('d m Y H:i');
440: }
441: $iEventStartTS = $oEventStartDT->getTimestamp();
442: }
443: }
444: }
445:
446: $sSubject = $this->getSubject($oUser, $sEventStart, $iEventStartTS, $sEventName, $sDate, $iNowTS, $bAllDay);
447:
448: $aUsers = array(
449: $oUser->IdUser => $oUser
450: );
451:
452: $aCalendarUsers = $this->oApiCalendarManager->getCalendarUsers($oUser, $oCalendar);
453: if (0 < count($aCalendarUsers)) {
454: foreach ($aCalendarUsers as $aCalendarUser) {
455: $oCalendarUser = $this->getUser($aCalendarUser['email']);
456: if ($oCalendarUser) {
457: $aUsers[$oCalendarUser->IdUser] = $oCalendarUser;
458: }
459: }
460: }
461:
462: foreach ($aUsers as $oUserItem) {
463: $bIsMessageSent = $this->sendMessage($oUserItem, $sSubject, $sEventName, $sDate, $oCalendar->DisplayName, $sEventText, $oCalendar->Color);
464: if ($bIsMessageSent) {
465: $sEventUrl = (substr(strtolower($sEventId), -4) !== '.ics') ? $sEventId . '.ics' : $sEventId;
466: $this->oApiCalendarManager->updateReminder($oUserItem->PublicId, $sCalendarUri, $sEventUrl, $vCal->serialize());
467: \Aurora\System\Api::Log('Send reminder for event: \''.$sEventName.'\' started on \''.$sDate.'\' to \''.$oUserItem->PublicId.'\'', \Aurora\System\Enums\LogLevel::Full, 'cron-');
468: } else {
469: \Aurora\System\Api::Log('Send reminder for event: FAILED!', \Aurora\System\Enums\LogLevel::Full, 'cron-');
470: }
471: }
472: } else {
473: \Aurora\System\Api::Log('Calendar '.$sCalendarUri.' not found!', \Aurora\System\Enums\LogLevel::Full, 'cron-');
474: }
475: }
476: }
477: }
478: }
479: }
480: }
481:
482: file_put_contents($this->sCurRunFilePath, $iNowTS);
483: }
484:
485: \Aurora\System\Api::Log('---------- End cron script', \Aurora\System\Enums\LogLevel::Full, 'cron-');
486: }
487: }
488:
489: $iTimer = microtime(true);
490:
491: Reminder::NewInstance()->Execute();
492:
493: \Aurora\System\Api::Log('Cron execution time: '.(microtime(true) - $iTimer).' sec.', \Aurora\System\Enums\LogLevel::Full, 'cron-');
494: