/home/ivoiecob/email.hirewise-va.com/modules/CalendarMeetingsPlugin/Module.php
<?php
/**
* This code is licensed under AGPLv3 license or Afterlogic Software License
* if commercial version of the product was purchased.
* For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
*/
namespace Aurora\Modules\CalendarMeetingsPlugin;
use Aurora\Modules\Core\Module as CoreModule;
use Aurora\Modules\Calendar\Module as CalendarModule;
use Aurora\Modules\Calendar\Classes\Helper as CalendarHelper;
use Aurora\System\Api;
use MailSo\Mime\Email;
/**
* @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
* @license https://afterlogic.com/products/common-licensing Afterlogic Software License
* @copyright Copyright (c) 2023, Afterlogic Corp.
*
* @property Settings $oModuleSettings
*
* @package Modules
*/
class Module extends \Aurora\System\Module\AbstractModule
{
/** @var Manager */
protected $oManager = null;
public $oApiFileCache = null;
// public $oApiCalendarDecorator = null;
// public $oApiUsersManager = null;
/**
* @return Module
*/
public static function getInstance()
{
return parent::getInstance();
}
/**
* @return Module
*/
public static function Decorator()
{
return parent::Decorator();
}
/**
* @return Settings
*/
public function getModuleSettings()
{
return $this->oModuleSettings;
}
public function getManager()
{
if ($this->oManager === null) {
$this->oManager = new Manager($this);
}
return $this->oManager;
}
public function getCacheManager()
{
if ($this->oApiFileCache === null) {
$this->oApiFileCache = new \Aurora\System\Managers\Filecache();
}
return $this->oApiFileCache;
}
public function init()
{
$this->AddEntries([
'invite' => 'EntryInvite'
]);
$this->subscribeEvent('Calendar::CreateIcs', array($this, 'onCreateIcs'));
$this->subscribeEvent('Calendar::populateVCalendar', array($this, 'onPopulateVCalendar'));
$this->subscribeEvent('Calendar::DeleteEvent', array($this, 'onDeleteEvent'));
$this->subscribeEvent('Calendar::UpdateEventAttendees', array($this, 'onUpdateEventAttendees'));
$this->subscribeEvent('CalendarMeetingsPlugin::processICS::UpdateEvent', array($this, 'onProcessICSUpdateEvent'));
$this->subscribeEvent('CalendarMeetingsPlugin::processICS::Cancel', array($this, 'onProcessICSCancel'));
$this->subscribeEvent('Calendar::processICS::UpdateEvent', array($this, 'onProcessICSUpdateEvent'));
$this->subscribeEvent('Calendar::processICS::Cancel', array($this, 'onProcessICSCancel'));
$this->subscribeEvent('Calendar::processICS::AddAttendeesToResult', array($this, 'onAddAttendeesToResult'));
$this->subscribeEvent('Calendar::parseEvent', array($this, 'onParseEvent'));
$this->aErrors = [
Enums\ErrorCodes::CannotSendAppointmentMessage => $this->i18N('ERROR_CANNOT_SEND_APPOINTMENT_MESSAGE'),
Enums\ErrorCodes::CannotSendAppointmentMessageNoOrganizer => $this->i18N('ERROR_CANNOT_SEND_APPOINTMENT_MESSAGE_NO_ORGANIZER')
];
}
/**
* @param int $UserId
* @param string $File
* @param string $FromEmail
* @return boolean
* @throws \Aurora\System\Exceptions\ApiException
*/
public function UpdateAttendeeStatus($UserId, $File, $FromEmail)
{
\Aurora\System\Api::CheckAccess($UserId);
$sUserPublicId = \Aurora\System\Api::getUserPublicIdById($UserId);
$mResult = false;
if (empty($File) || empty($FromEmail)) {
throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
}
$sData = $this->getCacheManager()->get($sUserPublicId, $File, '', CalendarModule::GetName());
if (!empty($sData)) {
$mResult = $this->getManager()->processICS($sUserPublicId, $sData, $FromEmail, true);
}
return $mResult;
}
/**
*
* @param int $UserId
* @param string $CalendarId
* @param string $EventId
* @param string $File
* @param string $AppointmentAction
* @param string $Attendee
* @return array|boolean
* @throws \Aurora\System\Exceptions\ApiException
*/
public function SetAppointmentAction($UserId, $CalendarId, $EventId, $File, $AppointmentAction, $Attendee, $AllEvents = 2, $RecurrenceId = null)
{
\Aurora\System\Api::CheckAccess($UserId);
$sUserPublicId = \Aurora\System\Api::getUserPublicIdById($UserId);
$mResult = false;
if (empty($AppointmentAction) || empty($CalendarId)) {
throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
}
$sData = '';
if (!empty($EventId)) {
$aEventData = $this->getManager()->getEvent($sUserPublicId, $CalendarId, $EventId);
if (isset($aEventData) && isset($aEventData['vcal']) && $aEventData['vcal'] instanceof \Sabre\VObject\Component\VCalendar) {
$oVCal = $aEventData['vcal'];
$oVCal->METHOD = 'REQUEST';
$sData = $oVCal->serialize();
}
} elseif (!empty($File)) {
$sData = $this->getCacheManager()->get($sUserPublicId, $File, '', CalendarModule::GetName());
}
if (!empty($sData)) {
$mProcessResult = $this->getManager()->appointmentAction($sUserPublicId, $Attendee, $AppointmentAction, $CalendarId, $sData, $AllEvents, $RecurrenceId);
if ($mProcessResult) {
$mResult = array(
'Uid' => $mProcessResult
);
}
}
return $mResult;
}
public function EntryInvite()
{
$sResult = '';
$aInviteValues = \Aurora\System\Api::DecodeKeyValues($this->oHttp->GetQuery('invite'));
if (isset($aInviteValues['organizer'])) {
$sOrganizerPublicId = $aInviteValues['organizer'];
$oOrganizerUser = CoreModule::Decorator()->GetUserByPublicId($sOrganizerPublicId);
if ($sOrganizerPublicId && isset($aInviteValues['attendee'], $aInviteValues['eventId'], $aInviteValues['action'])) {
$calendarId = $aInviteValues['calendarId'];
if (empty($calendarId)) {
$calendarId = $this->getManager()->findEventInCalendars($sOrganizerPublicId, $aInviteValues['eventId']);
}
$sAction = '';
$oEvent = $this->getManager()->getEvent($sOrganizerPublicId, $calendarId, $aInviteValues['eventId']);
$oModuleManager = \Aurora\System\Api::GetModuleManager();
$sTheme = $oModuleManager->getModuleConfigValue('CoreWebclient', 'Theme');
if ($oEvent && is_array($oEvent) && 0 < count($oEvent) && isset($oEvent[0])) {
$sResult = \file_get_contents($this->GetPath() . '/templates/CalendarEventInviteExternal.html');
if (is_string($sResult)) {
$dt = new \DateTime();
$dt->setTimestamp($oEvent[0]['startTS']);
if (!$oEvent[0]['allDay']) {
$sDefaultTimeZone = new \DateTimeZone($oOrganizerUser->DefaultTimeZone);
$dt->setTimezone($sDefaultTimeZone);
}
$sAction = $aInviteValues['action'];
$sActionColor = 'green';
$sActionText = '';
switch (strtoupper($sAction)) {
case 'ACCEPTED':
$sActionColor = 'green';
$sActionText = 'Accepted';
break;
case 'DECLINED':
$sActionColor = 'red';
$sActionText = 'Declined';
break;
case 'TENTATIVE':
$sActionColor = '#A0A0A0';
$sActionText = 'Tentative';
break;
}
$sDateFormat = 'm/d/Y';
$sTimeFormat = 'h:i A';
switch ($oOrganizerUser->DateFormat) {
case \Aurora\System\Enums\DateFormat::DDMMYYYY:
$sDateFormat = 'd/m/Y';
break;
case \Aurora\System\Enums\DateFormat::DD_MONTH_YYYY:
$sDateFormat = 'd/m/Y';
break;
default:
$sDateFormat = 'm/d/Y';
break;
}
switch ($oOrganizerUser->TimeFormat) {
case \Aurora\System\Enums\TimeFormat::F24:
$sTimeFormat = 'H:i';
break;
case \Aurora\System\Enums\DateFormat::DD_MONTH_YYYY:
\Aurora\System\Enums\TimeFormat::F12;
$sTimeFormat = 'h:i A';
break;
default:
$sTimeFormat = 'h:i A';
break;
}
$sDateTime = $dt->format($sDateFormat . ' ' . $sTimeFormat);
$mResult = array(
'{{EVENT_NAME}}' => $oEvent[0]['subject'],
'{{EVENT_BEGIN}}' => ucfirst($this->i18N('EVENT_BEGIN')),
'{{EVENT_DATE}}' => $sDateTime,
'{{CALENDAR}}' => ucfirst($this->i18N('CALENDAR')),
'{{EVENT_DESCRIPTION}}' => $oEvent[0]['description'],
'{{EVENT_ACTION}}' => $sActionText,
'{{ACTION_COLOR}}' => $sActionColor,
'{{THEME_NAME}}' => $sTheme,
);
$sResult = strtr($sResult, $mResult);
} else {
$sResult = "Error occured";
\Aurora\System\Api::Log('Empty page template.', \Aurora\System\Enums\LogLevel::Error);
}
} else {
\Aurora\System\Api::Log('Event not found.', \Aurora\System\Enums\LogLevel::Error);
$sResult = file_get_contents($this->GetPath() . '/templates/EventNotFound.html');
if (is_string($sResult)) {
$mResult = array(
'{{INFO}}' => ucfirst($this->i18N('ERROR_APPOINTMENT_NOT_FOUND')),
'{{THEME_NAME}}' => $sTheme,
);
$sResult = strtr($sResult, $mResult);
} else {
$sResult = "Error occured";
\Aurora\System\Api::Log('Empty page template.', \Aurora\System\Enums\LogLevel::Error);
}
}
$sAttendee = $aInviteValues['attendee'];
if (!empty($sAttendee)) {
$oAttendee = Email::Parse($sAttendee);
$sAttendee = $oAttendee->GetEmail();
if (isset($oEvent) && isset($oEvent['vcal']) && $oEvent['vcal'] instanceof \Sabre\VObject\Component\VCalendar) {
$oVCal = $oEvent['vcal'];
$oVCal->METHOD = 'REQUEST';
$sData = $oVCal->serialize();
$oAttendeeUser = CoreModule::Decorator()->GetUserByPublicId($sAttendee);
$bIsExternalAttendee = false;
if ($oAttendeeUser) {
$sOrganizerPublicId = $oAttendeeUser->PublicId;
} else {
$bIsExternalAttendee = true;
}
$actionResult = false;
try {
$actionResult = $this->getManager()->appointmentAction($sOrganizerPublicId, $sAttendee, $sAction, $calendarId, $sData, 2, null, true, $bIsExternalAttendee);
} catch (\Aurora\System\Exceptions\Exception $e) {
Api::LogException($e);
$actionResult = false;
}
if (!$actionResult) {
$sResult = file_get_contents($this->GetPath() . '/templates/EventNotFound.html');
if (is_string($sResult)) {
$mResult = array(
'{{INFO}}' => ucfirst($this->i18N('ERROR_APPOINTMENT_UPDATE_STATUS')),
'{{THEME_NAME}}' => $sTheme,
);
$sResult = strtr($sResult, $mResult);
}
}
}
}
}
}
return $sResult;
}
/**
*
* @return array
*/
public function GetSettings()
{
\Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
$aSettings = array(
'AllowAppointments' => $this->oModuleSettings->AllowAppointments
);
return $aSettings;
}
public function onCreateIcs($aData, &$oIcs)
{
$oIcs->Attendee = isset($aData['Attendee']) ? $aData['Attendee'] : null;
$oIcs->Type = $aData['Action'];
}
public function onPopulateVCalendar(&$aData, &$oVEvent)
{
/** @var \Sabre\VObject\Component\VEvent $oVEvent */
$oEvent = & $aData['oEvent'];
$oUser = \Aurora\System\Api::getAuthenticatedUser();
$aAttendees = [];
$aAttendeeEmails = [];
$aObjAttendees = [];
if (isset($oVEvent->ORGANIZER)) {
$sOwnerEmail = str_replace('mailto:', '', strtolower((string) $oVEvent->ORGANIZER));
$iPos = strpos($sOwnerEmail, 'principals/');
if ($iPos !== false) {
$sOwnerEmail = \trim(substr($sOwnerEmail, $iPos + 11), '/');
}
}
if (isset($oVEvent->ATTENDEE)) {
$aAttendeeEmails = [];
foreach ($oEvent->Attendees as $aItem) {
$sStatus = '';
switch ($aItem['status']) {
case \Aurora\Modules\Calendar\Enums\AttendeeStatus::Accepted:
$sStatus = 'ACCEPTED';
break;
case \Aurora\Modules\Calendar\Enums\AttendeeStatus::Declined:
$sStatus = 'DECLINED';
break;
case \Aurora\Modules\Calendar\Enums\AttendeeStatus::Tentative:
$sStatus = 'TENTATIVE';
break;
case \Aurora\Modules\Calendar\Enums\AttendeeStatus::Unknown:
$sStatus = 'NEEDS-ACTION';
break;
}
$aAttendeeEmails[strtolower($aItem['email'])] = $sStatus;
}
$aObjAttendees = $oVEvent->ATTENDEE;
unset($oVEvent->ATTENDEE);
foreach ($aObjAttendees as $oAttendee) {
$sAttendee = str_replace('mailto:', '', strtolower((string)$oAttendee));
$oPartstat = $oAttendee->offsetGet('PARTSTAT');
if (in_array($sAttendee, array_keys($aAttendeeEmails))) {
if (isset($oPartstat) && (string)$oPartstat === $aAttendeeEmails[$sAttendee]) {
$oVEvent->add($oAttendee);
$aAttendees[] = $sAttendee;
}
}
}
}
if (isset($oEvent->Attendees) && count($oEvent->Attendees) > 0) {
if (!isset($oVEvent->ORGANIZER)) {
$oVEvent->ORGANIZER = 'mailto:' . $oUser->PublicId;
}
foreach ($oEvent->Attendees as $oAttendee) {
if (!in_array($oAttendee['email'], $aAttendees)) {
$oVEvent->add(
'ATTENDEE',
'mailto:' . $oAttendee['email'],
array(
'CN' => !empty($oAttendee['name']) ? $oAttendee['name'] : $oAttendee['email'],
'RSVP' => 'TRUE'
)
);
}
}
} else {
unset($oVEvent->ORGANIZER);
}
}
public function onDeleteEvent($aData, &$oVCal)
{
$sUserPublicId = $aData['sUserPublicId'];
/** @var \Sabre\VObject\Component\VCalendar $oVCal */
$iIndex = CalendarHelper::getBaseVComponentIndex($oVCal->VEVENT);
if ($iIndex !== false) {
/** @var \Sabre\VObject\Component\VEvent $oVEvent */
$oVEvent = $oVCal->VEVENT[$iIndex];
$sOrganizer = (isset($oVEvent->ORGANIZER)) ?
str_replace('mailto:', '', strtolower((string)$oVEvent->ORGANIZER)) : null;
$iPos = false;
if (!empty($sOrganizer)) {
$iPos = strpos($sOrganizer, 'principals/');
}
if ($iPos !== false) {
$sOrganizer = \trim(substr($sOrganizer, $iPos + 11), '/');
}
if (isset($sOrganizer)) {
if ($sOrganizer === $sUserPublicId) {
$oDateTimeNow = new \DateTimeImmutable("now");
/** @var \Sabre\VObject\Property\ICalendar\DateTime $oDTSTART */
$oDTSTART = $oVEvent->DTSTART;
$oDateTimeEvent = $oDTSTART->getDateTime();
$oDateTimeRepeat = CalendarHelper::getNextRepeat($oDateTimeNow, $oVEvent);
$bRrule = isset($oVEvent->RRULE);
$bEventFore = $oDateTimeEvent ? $oDateTimeEvent > $oDateTimeNow : false;
$bNextRepeatFore = $oDateTimeRepeat ? $oDateTimeRepeat > $oDateTimeNow : false;
if (isset($oVEvent->ATTENDEE) && ($bRrule ? $bNextRepeatFore : $bEventFore)) {
foreach ($oVEvent->ATTENDEE as $oAttendee) {
$sEmail = str_replace('mailto:', '', strtolower((string)$oAttendee));
$oVCalResult = clone $oVCal;
$oVCalResult->METHOD = 'CANCEL';
$sSubject = (string)$oVEvent->SUMMARY . ': Canceled';
Classes\Helper::sendAppointmentMessage($sUserPublicId, $sEmail, $sSubject, $oVCalResult, 'REQUEST');
unset($oVCal->METHOD);
}
}
}
}
}
}
public function onUpdateEventAttendees($aData, &$oEvent)
{
$aEventData = $this->getManager()->getEvent($aData['UserPublicId'], $oEvent->IdCalendar, $oEvent->Id);
if (isset($aEventData[0]) && isset($aEventData[0]['attendees'])) {
$oldAttendees = $aEventData[0]['attendees'];
$newAttendees = @json_decode($aData['attendees'], true);
$deleteAttendees = [];
foreach ($oldAttendees as $old) {
$found = false;
foreach ($newAttendees as $new) {
if (strtolower($new['email']) === strtolower($old['email'])) {
$found = true;
break;
}
}
if (!$found) {
$deleteAttendees[] = $old;
}
}
$oVCalResult = clone $aEventData['vcal'];
$oVCalResult->METHOD = 'CANCEL';
if (isset($oVCalResult->VEVENT)) {
$sSubject = (string) $oVCalResult->VEVENT->SUMMARY . ': Canceled';
$oVCalResult->VEVENT->SEQUENCE = (int) $oVCalResult->VEVENT->SEQUENCE->getValue() + 1;
foreach ($deleteAttendees as $deleteAttendee) {
$sEmail = $deleteAttendee['email'];
unset($oVCalResult->VEVENT->ATTENDEE);
$oVCalResult->VEVENT->add(
'ATTENDEE',
'mailto:' . $deleteAttendee['email'],
array(
'CN' => !empty($deleteAttendee['name']) ? $deleteAttendee['name'] : $deleteAttendee['email'],
'PARTSTAT' => 'DECLINED'
)
);
Classes\Helper::sendAppointmentMessage($aData['UserPublicId'], $sEmail, $sSubject, $oVCalResult, 'REQUEST');
}
}
}
$oEvent->Attendees = @json_decode($aData['attendees'], true);
}
public function onProcessICSUpdateEvent(&$aData, &$mResult)
{
$oVCalResult = $aData['oVCalResult'];
$oVEventResult = $aData['oVEventResult'];
$sUserPublicId = $aData['sUserPublicId'];
$sCalendarId = $aData['sCalendarId'];
$sEventId = $aData['sEventId'];
$sMethod = $aData['sMethod'];
$sequence = $aData['sequence'];
$sequenceServer = $aData['sequenceServer'];
/** @var \Sabre\VObject\Component\VEvent $oVEvent */
$oVEvent = $aData['oVEvent'];
$mFromEmail = $aData['mFromEmail'];
$sType = $sMethod;
$oCurrentAttendee = null;
if (isset($oVEvent->ATTENDEE) && $sequenceServer >= $sequence) {
foreach ($oVEvent->ATTENDEE as $oAttendee) {
if ($mFromEmail && $mFromEmail === str_replace('mailto:', '', strtolower((string) $oAttendee->getValue()))) {
$oCurrentAttendee = $oAttendee;
break;
}
}
if (isset($oVEventResult->ATTENDEE, $oCurrentAttendee)) {
foreach ($oVEventResult->ATTENDEE as &$oAttendeeResult) {
if ($oAttendeeResult->getValue() === $oCurrentAttendee->getValue()) {
if (isset($oCurrentAttendee['PARTSTAT'])) {
$oAttendeeResult['PARTSTAT'] = $oCurrentAttendee['PARTSTAT']->getValue();
$sType = $sType . '-' . (string) $oAttendeeResult['PARTSTAT'];
/** @var \Sabre\VObject\Property\ICalendar\DateTime $oLAST_MODIFIED */
$oLAST_MODIFIED = $oVEvent->{'LAST-MODIFIED'};
$oRespondedAt = $oLAST_MODIFIED->getDateTime();
$oRespondedAt->setTimezone(new \DateTimeZone('UTC'));
$oAttendeeResult['RESPONDED-AT'] = gmdate("Ymd\THis\Z", $oRespondedAt->getTimestamp());
}
break;
}
}
}
}
unset($oVCalResult->METHOD);
$oVEventResult->{'LAST-MODIFIED'} = new \DateTime('now', new \DateTimeZone('UTC'));
$mResult = $this->getManager()->updateEventRaw($sUserPublicId, $sCalendarId, $sEventId, $oVCalResult->serialize());
if ($mResult) {
$mResult = $sType;
}
// $oVCalResult->METHOD = $sMethod;
}
public function onProcessICSCancel(&$aData, &$mResult)
{
$sCalendarId = $aData['sCalendarId'];
$sUserPublicId = $aData['sUserPublicId'];
$sEventId = $aData['sEventId'];
if ($this->getManager()->deleteEvent($sUserPublicId, $sCalendarId, $sEventId)) {
$mResult = true;
}
}
public function onAddAttendeesToResult(&$aData, &$mResult)
{
$oVEventResult = $aData['oVEventResult'];
$aAccountEmails = $aData['aAccountEmails'];
$sMethod = $aData['sMethod'];
if (isset($oVEventResult->ATTENDEE)) {
foreach ($oVEventResult->ATTENDEE as $oAttendee) {
$sAttendee = strtolower((string)$oAttendee);
if (in_array($sAttendee, $aAccountEmails) && isset($oAttendee['PARTSTAT']) && $sMethod !== 'SAVE') {
$mResult['Attendee'] = str_replace('mailto:', '', $sAttendee);
$mResult['Action'] = $sMethod . '-' . $oAttendee['PARTSTAT']->getValue();
}
}
}
}
public function onParseEvent(&$aData, &$aEvent)
{
$oVComponent = $aData['oVComponent'];
$sOwnerEmail = $aData['sOwnerEmail'];
$oUser = $aData['oUser'];
$bIsAppointment = false;
$aEvent['attendees'] = array();
if (isset($oVComponent->ATTENDEE)) {
$aEvent['attendees'] = \Aurora\Modules\Calendar\Classes\Parser::parseAttendees($oVComponent);
if (isset($oVComponent->ORGANIZER)) {
$sOwnerEmail = str_replace('mailto:', '', strtolower((string)$oVComponent->ORGANIZER));
$iPos = strpos($sOwnerEmail, 'principals/');
if ($iPos !== false) {
$sOwnerEmail = \trim(substr($sOwnerEmail, $iPos + 11), '/');
}
$aData['sOwnerEmail'] = $sOwnerEmail;
$aEvent['organizer'] = $sOwnerEmail;
$aEvent['organizerName'] = isset($oVComponent->ORGANIZER['CN']) ? (string)$oVComponent->ORGANIZER['CN'] : '';
}
$bIsAppointment = ($oUser instanceof \Aurora\Modules\Core\Models\User && $sOwnerEmail !== $oUser->PublicId);
}
$aEvent['appointment'] = $bIsAppointment;
$aEvent['appointmentAccess'] = 0;
}
}