get('msg_struct')) { get_calendar_part_imap($this->get('msg_struct'), $this); } if ($this->get('calendar_event_raw')) { $data = Tiki\SabreDav\Utilities::getDenormalizedData($this->get('calendar_event_raw'), TikiLib::lib('tiki')->get_display_timezone()); $this->out('calendar_event', $data); } $recipient = null; $headers = $this->get('msg_headers', array()); foreach ($headers as $name => $value) { if (strtolower($name) == 'to') { $recipient = (string)$value; } } $this->out('recipient', $recipient); } } /** * Send a RSVP for an event * @subpackage tiki/handler */ class Hm_Handler_event_rsvp_action extends Hm_Handler_Module { public function process() { list($success, $form) = $this->process_form(array('rsvp_action')); if (! $success) { return; } $recipient = $this->get('recipient'); $calendardata = $this->get('calendar_event_raw'); if (! $calendardata) { return; } // format answer $partstat = null; $action = ""; switch ($form['rsvp_action']) { case 'accept': $partstat = 'ACCEPTED'; $action = 'accepted'; break; case 'maybe': $partstat = 'TENTATIVE'; $action = 'tentatively accepted'; break; case 'decline': $partstat = 'DECLINED'; $action = 'declined'; break; } // parse event and format response $vObject = Sabre\VObject\Reader::read($calendardata); $vObject->method = 'REPLY'; foreach ($vObject->getComponents() as $component) { if ($component->name !== 'VEVENT') { continue; } if (isset($component->ATTENDEE)) { foreach ($component->ATTENDEE as $attendee) { $email = preg_replace("/MAILTO:\s*/i", "", (string)$attendee); if ($email === $recipient && $partstat) { $attendee['PARTSTAT'] = $partstat; unset($attendee['RSVP']); $component->ATTENDEE = $attendee; } } } $component->DTSTAMP = \DateTime::createFromFormat('U', time())->format('Ymd\THis\Z'); } $event_response = $vObject->serialize(); $vObject->destroy(); // format reply, smtp server details and recipients list($to, $cc, $subject, $body, $in_reply_to) = format_reply_fields( $this->get('msg_text'), $this->get('msg_headers'), $this->get('msg_struct_current'), false, new Hm_Output_add_rsvp_actions($this->output, $this->protected), 'reply' ); $profiles = $this->get('compose_profiles', array()); $recip = get_primary_recipient($profiles, $this->get('msg_headers'), $this->get('smtp_servers', array())); $profile_index = $default_profile_index = null; foreach ($profiles as $index => $profile) { if ($profile['address'] == $recip) { $profile_index = $index; } if (! empty($profile['default'])) { $default_profile_index = $index; } } if (is_null($profile_index)) { $profile_index = $default_profile_index; } if (! is_null($profile_index)) { $smtp_id = $profiles[$profile_index]['smtp_id']; $compose_smtp_id = $smtp_id . '.' . ($profile_index + 1); } else { $smtp_id = 0; $compose_smtp_id = null; } // smtp server details $smtp_details = Hm_SMTP_List::dump($smtp_id, true); if (! $smtp_details) { Hm_Msgs::add('ERRCould not use the configured SMTP server'); return; } // profile details list($imap_server, $from_name, $reply_to, $from) = get_outbound_msg_profile_detail(['compose_smtp_id' => $compose_smtp_id], $profiles, $smtp_details, $this); // xoauth2 check smtp_refresh_oauth2_token_on_send($smtp_details, $this, $smtp_id); // adjust from and reply to addresses list($from, $reply_to) = outbound_address_check($this, $from, $reply_to); // use specific text body for the reply $event = $this->get('calendar_event'); $body = "$from_name has $action the invitation to the following event: *{$event['name']}* When: " . TikiLib::lib('tiki')->get_long_datetime($event['start']) . " - " . TikiLib::lib('tiki')->get_long_datetime($event['end']) . " Invitees: " . implode(",\n", $event['attendees']); // try to connect $smtp = Hm_SMTP_List::connect($smtp_id, false); if (! smtp_authed($smtp)) { Hm_Msgs::add("ERRFailed to authenticate to the SMTP server"); return; } // build message $mime = new Hm_MIME_Msg($to, $subject, $body, $from, 0, $cc, '', $in_reply_to, $from_name, $reply_to); // add attachments $content = Hm_Crypt::ciphertext($event_response, Hm_Request_Key::generate()); $filename = hash('sha512', $content); $filepath = rtrim($this->config->get('attachment_dir'), '/'); if (@file_put_contents($filepath . '/' . $filename, $content)) { $file = [ 'filename' => $filepath . '/' . $filename, 'basename' => $filename, 'type' => 'text/calendar; method=REPLY', 'name' => 'event.ics', 'no_encoding' => true, ]; $mime->add_attachments([$file]); } else { $file = null; } // get smtp recipients $recipients = $mime->get_recipient_addresses(); if (empty($recipients)) { Hm_Msgs::add("ERRNo valid receipts found"); return; } // send the message $err_msg = $smtp->send_message($from, $recipients, $mime->get_mime_msg()); if ($err_msg) { Hm_Msgs::add(sprintf("ERR%s", $err_msg)); return; } if (! empty($file['filename'])) { @unlink($file['filename']); } // sync partstat for local calendar event global $prefs, $user; if ($prefs['feature_calendar'] === 'y') { $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if ($existing) { $event['calendarId'] = $existing['calendarId']; $event['calitemId'] = $existing['calitemId']; if (! empty($event['participants'])) { foreach ($event['participants'] as &$role) { if ($role['email'] === $recipient && $partstat) { $role['partstat'] = $partstat; } } } if ($existing['recurrenceId']) { $rec = new CalRecurrence($existing['recurrenceId']); if ($event['rec']) { $event['rec']->setId($rec->getId()); $event['rec']->setUri($rec->getUri()); $rec = $event['rec']; } $rec->updateDetails($event); $rec->setUser($user); $rec->save(true); $rec->updateOverrides($event['overrides']); } else { TikiLib::lib('calendar')->set_item($user, $event['calitemId'], $event); } } } } } /** * Add an event to Tiki calendar * @subpackage tiki/handler */ class Hm_Handler_add_to_calendar extends Hm_Handler_Module { public function process() { global $prefs, $user; if ($prefs['feature_calendar'] !== 'y') { return; } list($success, $form) = $this->process_form(array('calendar_id')); if (! $success) { Hm_Msgs::add("ERRNo calendar selected"); return; } $calendar = TikiLib::lib('calendar')->get_calendar($form['calendar_id']); if (! $calendar) { Hm_Msgs::add("ERRSelected calendar is unavailable"); return; } $perms = Perms::get('calendar', $form['calendar_id']); if (! $perms->add_events) { Hm_Msgs::add("ERRInsufficient permissions to create the event in the selected calendar"); return; } $data = $this->get('calendar_event'); $data['calendarId'] = $form['calendar_id']; if ($data['rec']) { if (empty($data['priority'])) { $data['priority'] = 0; } if (is_null($data['status'])) { $data['status'] = 1; } if (empty($data['lang'])) { $data['lang'] = 'en'; } if (empty($data['nlId'])) { $data['nlId'] = 0; } $data['user'] = $user; $rec = $data['rec']; $rec->updateDetails($data); $rec->save(true); $rec->updateOverrides($data['overrides']); } else { TikiLib::lib('calendar')->set_item($user, 0, $data); } Hm_Msgs::add("Event created"); } } /** * Update participant status for a Tiki calendar event * @subpackage tiki/handler */ class Hm_Handler_update_participant_status extends Hm_Handler_Module { public function process() { global $prefs; if ($prefs['feature_calendar'] !== 'y') { return; } $event = $this->get('calendar_event'); $from = null; $headers = $this->get('msg_headers', array()); foreach ($headers as $name => $value) { if (strtolower($name) == 'from') { $from = (string)$value; } } $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if ($existing) { if (! empty($event['participants'])) { foreach ($event['participants'] as &$role) { if ($role['email'] === $from) { TikiLib::lib('calendar')->update_partstat($existing['calitemId'], $role['username'], $role['partstat']); } } } } Hm_Msgs::add("Information updated"); } } /** * Remove an event from Tiki calendar when cancelation email is received * @subpackage tiki/handler */ class Hm_Handler_remove_from_calendar extends Hm_Handler_Module { public function process() { global $prefs, $user; if ($prefs['feature_calendar'] !== 'y') { return; } $event = $this->get('calendar_event'); $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if ($existing) { TikiLib::lib('calendar')->drop_item($user, $existing['calitemId'], false, false); } Hm_Msgs::add("Event removed"); } } /** * Show RSVP buttons if message contains a calendar invitation * @subpackage tiki/output */ class Hm_Output_add_rsvp_actions extends Hm_Output_Module { protected function output() { global $prefs, $user; $method = $this->get('calendar_method'); $event = $this->get('calendar_event'); $headers = $this->get('msg_headers'); if (! empty($event)) { $res = ''; $res .= sprintf('%s%s', tr('Event start'), TikiLib::lib('tiki')->get_long_datetime($event['start'])); $res .= sprintf('%s%s', tr('Event end'), TikiLib::lib('tiki')->get_long_datetime($event['end'])); $res .= sprintf('%s%s', tr('Organizer'), implode(", ", $event['real_organizers'])); if ($prefs['feature_calendar'] == 'y' && $method != 'CANCEL') { $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if (! $existing) { $options = ['']; $calendars = TikiLib::lib('calendar')->list_calendars(); $calendars['data'] = Perms::filter([ 'type' => 'calendar' ], 'object', $calendars['data'], [ 'object' => 'calendarId' ], 'add_events'); foreach ($calendars['data'] as $row) { $options[] = ""; } $res .= sprintf( '%s', tr('Add to calendar'), implode('', $options) ); } } if ($method == 'REQUEST') { $partstat = null; $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if ($existing) { $existing = TikiLib::lib('calendar')->get_item($existing['calitemId']); } if ($existing && ! empty($existing['participants'])) { foreach ($existing['participants'] as $role) { if ($role['email'] == $this->get('recipient')) { $partstat = $role['partstat']; } } } $yes_tag = $maybe_tag = $no_tag = "a"; if ($partstat == 'ACCEPTED') { $yes_tag = "span"; } if ($partstat == 'TENTATIVE') { $maybe_tag = "span"; } if ($partstat == 'DECLINED') { $no_tag = "span"; } $res .= sprintf( '%s<%s class="event_rsvp_link hlink" data-action="accept" href="#">%s | <%s class="event_rsvp_link hlink" data-action="maybe" href="#">%s | <%s class="event_rsvp_link hlink" data-action="decline" href="#">%s', tr('RSVP'), $yes_tag, tr('Yes'), $yes_tag, $maybe_tag, tr('Maybe'), $maybe_tag, $no_tag, tr('No'), $no_tag ); } if ($prefs['feature_calendar'] == 'y' && $method == 'REPLY') { $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if($existing){ $participants = TikiLib::lib('calendar')->get_participant_by_event_uid($event['uid']); $participant_status_updated = true; if (!empty($existing['participants'])) { foreach ($event['participants'] as $role) { $idx = array_search($role['username'] , array_column($participants, 'username')); if(!$idx){ $idx = array_search($role['email'] , array_column($participants, 'username')); } if($idx && $role['partstat'] != $participants[$idx]['partstat']){ $participant_status_updated = false; break; } } } $event_update_participant_class = 'event_update_participant_status'; $event_update_participant_text = 'Update participant status'; if($participant_status_updated){ $event_update_participant_class = 'event_participant_status_updated'; $event_update_participant_text = 'Participant status updated'; } $res .= sprintf( ' %s ', tr($event_update_participant_text) ); } } if ($prefs['feature_calendar'] == 'y' && $method == 'CANCEL') { $existing = TikiLib::lib('calendar')->find_by_uid(null, $event['uid']); if ($existing) { $res .= sprintf( '%s', tr('Remove from calendar') ); } } $headers = preg_replace("#]*header_space[^>]*>.*?#", $res . "\\0", $headers); } $this->out('msg_headers', $headers, false); } } /** * Search imap message structure for text/calendar parts * @subpackage tiki/functions * @param array $struct message structure * @param object $mod Hm_Handler_Module * @return string */ if (! hm_exists('get_calendar_part_imap')) { function get_calendar_part_imap($struct, $mod) { $event = $method = null; $part = false; foreach ($struct as $id => $vals) { if (is_array($vals) && isset($vals['type'])) { if ($vals['type'] . '/' . $vals['subtype'] == 'text/calendar') { $part = $id; $method = $vals['attributes']['method']; } if (isset($vals['subs'])) { return get_calendar_part_imap($vals['subs'], $mod); } } else { if (is_array($vals) && count($vals) == 1 && isset($vals['subs'])) { return get_calendar_part_imap($vals['subs'], $mod); } } } if (! $part) { return; } list($success, $form) = $mod->process_form(array('imap_server_id', 'imap_msg_uid', 'folder')); if ($success) { $cache = Hm_IMAP_List::get_cache($mod->cache, $form['imap_server_id']); $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache); if (imap_authed($imap)) { if ($imap->select_mailbox(hex2bin($form['folder']))) { $msg_struct = $imap->get_message_structure($form['imap_msg_uid']); if ($part !== false) { if ($part == 0) { $max = 500000; } else { $max = false; } $struct = $imap->search_bodystructure($msg_struct, array('imap_part_number' => $part)); $msg_struct_current = array_shift($struct); $event = $imap->get_message_content($form['imap_msg_uid'], $part, $max, $msg_struct_current); } } } } $mod->out('calendar_method', $method); $mod->out('calendar_event_raw', $event); } }