/** * Add UI element to copy event invitations or updates to the calendar */ public function mail_messagebody_html($p) { // load iCalendar functions (if necessary) if (!empty($this->lib->ical_parts)) { $this->get_ical(); $this->load_itip(); } $html = ''; $has_events = false; $ical_objects = $this->lib->get_mail_ical_objects(); // show a box for every event in the file foreach ($ical_objects as $idx => $event) { if ($event['_type'] != 'event') { // skip non-event objects (#2928) continue; } $has_events = true; // get prepared inline UI for this event object if ($ical_objects->method) { $append = ''; // prepare a small agenda preview to be filled with actual event data on async request if ($ical_objects->method == 'REQUEST') { $append = html::div('calendar-agenda-preview', html::tag('h3', 'preview-title', $this->gettext('agenda') . ' ' . html::span('date', $this->rc->format_date($event['start'], $this->rc->config->get('date_format')))) . '%before%' . $this->mail_agenda_event_row($event, 'current') . '%after%'); } $html .= html::div('calendar-invitebox', $this->itip->mail_itip_inline_ui($event, $ical_objects->method, $ical_objects->mime_id . ':' . $idx, 'calendar', rcube_utils::anytodatetime($ical_objects->message_date), $this->rc->url(array('task' => 'calendar')) . '&view=agendaDay&date=' . $event['start']->format('U')) . $append); } // limit listing if ($idx >= 3) { break; } } // prepend event boxes to message body if ($html) { $this->ui->init(); $p['content'] = $html . $p['content']; $this->rc->output->add_label('calendar.savingdata', 'calendar.deleteventconfirm', 'calendar.declinedeleteconfirm'); } // add "Save to calendar" button into attachment menu if ($has_events) { $this->add_button(array('id' => 'attachmentsavecal', 'name' => 'attachmentsavecal', 'type' => 'link', 'wrapper' => 'li', 'command' => 'attachment-save-calendar', 'class' => 'icon calendarlink', 'classact' => 'icon calendarlink active', 'innerclass' => 'icon calendar', 'label' => 'calendar.savetocalendar'), 'attachmentmenu'); } return $p; }
/** * Handler for POST request to import an event attached to a mail message */ public function mail_import_itip() { $itip_sending = $this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']); $uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST); $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST); $mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST); $status = rcube_utils::get_input_value('_status', rcube_utils::INPUT_POST); $delete = intval(rcube_utils::get_input_value('_del', rcube_utils::INPUT_POST)); $noreply = intval(rcube_utils::get_input_value('_noreply', rcube_utils::INPUT_POST)); $noreply = $noreply || $status == 'needs-action' || $itip_sending === 0; $instance = rcube_utils::get_input_value('_instance', rcube_utils::INPUT_POST); $savemode = rcube_utils::get_input_value('_savemode', rcube_utils::INPUT_POST); $error_msg = $this->gettext('errorimportingevent'); $success = false; $delegate = null; if ($status == 'delegated') { $delegates = rcube_mime::decode_address_list(rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST, true), 1, false); $delegate = reset($delegates); if (empty($delegate) || empty($delegate['mailto'])) { $this->rc->output->command('display_message', $this->gettext('libcalendaring.delegateinvalidaddress'), 'error'); return; } } // successfully parsed events? if ($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) { // forward iTip request to delegatee if ($delegate) { $rsvpme = intval(rcube_utils::get_input_value('_rsvp', rcube_utils::INPUT_POST)); $itip = $this->load_itip(); if ($itip->delegate_to($event, $delegate, $rsvpme ? true : false)) { $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation'); } else { $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error'); } // the delegator is set to non-participant, thus save as non-blocking $event['free_busy'] = 'free'; } // find writeable calendar to store event $cal_id = !empty($_REQUEST['_folder']) ? rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST) : null; $dontsave = $_REQUEST['_folder'] === '' && $event['_method'] == 'REQUEST'; $calendars = $this->driver->list_calendars(calendar_driver::FILTER_PERSONAL); $calendar = $calendars[$cal_id]; // select default calendar except user explicitly selected 'none' if (!$calendar && !$dontsave) { $calendar = $this->get_default_calendar($event['sensitivity']); } $metadata = array('uid' => $event['uid'], '_instance' => $event['_instance'], 'changed' => is_object($event['changed']) ? $event['changed']->format('U') : 0, 'sequence' => intval($event['sequence']), 'fallback' => strtoupper($status), 'method' => $event['_method'], 'task' => 'calendar'); // update my attendee status according to submitted method if (!empty($status)) { $organizer = null; $emails = $this->get_user_emails(); foreach ($event['attendees'] as $i => $attendee) { if ($attendee['role'] == 'ORGANIZER') { $organizer = $attendee; } else { if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) { $event['attendees'][$i]['status'] = strtoupper($status); if (!in_array($event['attendees'][$i]['status'], array('NEEDS-ACTION', 'DELEGATED'))) { $event['attendees'][$i]['rsvp'] = false; } // unset RSVP attribute $metadata['attendee'] = $attendee['email']; $metadata['rsvp'] = $attendee['role'] != 'NON-PARTICIPANT'; $reply_sender = $attendee['email']; $event_attendee = $attendee; } } } // add attendee with this user's default identity if not listed if (!$reply_sender) { $sender_identity = $this->rc->user->list_emails(true); $event['attendees'][] = array('name' => $sender_identity['name'], 'email' => $sender_identity['email'], 'role' => 'OPT-PARTICIPANT', 'status' => strtoupper($status)); $metadata['attendee'] = $sender_identity['email']; } } // save to calendar if ($calendar && $calendar['editable']) { // check for existing event with the same UID $existing = $this->driver->get_event($event, calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_PERSONAL); if ($existing) { // forward savemode for correct updates of recurring events $existing['_savemode'] = $savemode ?: $event['_savemode']; // only update attendee status if ($event['_method'] == 'REPLY') { // try to identify the attendee using the email sender address $existing_attendee = -1; $existing_attendee_emails = array(); foreach ($existing['attendees'] as $i => $attendee) { $existing_attendee_emails[] = $attendee['email']; if ($event['_sender'] && ($attendee['email'] == $event['_sender'] || $attendee['email'] == $event['_sender_utf'])) { $existing_attendee = $i; } } $event_attendee = null; $update_attendees = array(); foreach ($event['attendees'] as $attendee) { if ($event['_sender'] && ($attendee['email'] == $event['_sender'] || $attendee['email'] == $event['_sender_utf'])) { $event_attendee = $attendee; $update_attendees[] = $attendee; $metadata['fallback'] = $attendee['status']; $metadata['attendee'] = $attendee['email']; $metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT'; if ($attendee['status'] != 'DELEGATED') { break; } } else { if (!empty($attendee['delegated-from']) && (stripos($attendee['delegated-from'], $event['_sender']) !== false || stripos($attendee['delegated-from'], $event['_sender_utf']) !== false)) { $update_attendees[] = $attendee; if (!in_array($attendee['email'], $existing_attendee_emails)) { $existing['attendees'][] = $attendee; } } } } // if delegatee has declined, set delegator's RSVP=True if ($event_attendee && $event_attendee['status'] == 'DECLINED' && $event_attendee['delegated-from']) { foreach ($existing['attendees'] as $i => $attendee) { if ($attendee['email'] == $event_attendee['delegated-from']) { $existing['attendees'][$i]['rsvp'] = true; break; } } } // found matching attendee entry in both existing and new events if ($existing_attendee >= 0 && $event_attendee) { $existing['attendees'][$existing_attendee] = $event_attendee; $success = $this->driver->update_attendees($existing, $update_attendees); } else { if (($event['sequence'] >= $existing['sequence'] || $event['changed'] >= $existing['changed']) && $event_attendee) { $existing['attendees'][] = $event_attendee; $success = $this->driver->update_attendees($existing, $update_attendees); } else { $error_msg = $this->gettext('newerversionexists'); } } } else { if ($status == 'declined' && $delete) { $deleted = $this->driver->remove_event($existing, true); $success = true; } else { if ($event['sequence'] >= $existing['sequence'] || $event['changed'] >= $existing['changed']) { $event['id'] = $existing['id']; $event['calendar'] = $existing['calendar']; // preserve my participant status for regular updates if (empty($status)) { $emails = $this->get_user_emails(); foreach ($event['attendees'] as $i => $attendee) { if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) { foreach ($existing['attendees'] as $j => $_attendee) { if ($attendee['email'] == $_attendee['email']) { $event['attendees'][$i] = $existing['attendees'][$j]; break; } } } } } // set status=CANCELLED on CANCEL messages if ($event['_method'] == 'CANCEL') { $event['status'] = 'CANCELLED'; } // show me as free when declined (#1670) if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT') { $event['free_busy'] = 'free'; } $success = $this->driver->edit_event($event); } else { if (!empty($status)) { $existing['attendees'] = $event['attendees']; if ($status == 'declined' || $event_attendee['role'] == 'NON-PARTICIPANT') { // show me as free when declined (#1670) $existing['free_busy'] = 'free'; } $success = $this->driver->edit_event($existing); } else { $error_msg = $this->gettext('newerversionexists'); } } } } } else { if (!$existing && ($status != 'declined' || $this->rc->config->get('kolab_invitation_calendars'))) { if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT') { $event['free_busy'] = 'free'; } // if the RSVP reply only refers to a single instance: // store unmodified master event with current instance as exception if (!empty($instance) && !empty($savemode) && $savemode != 'all') { $master = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event'); if ($master['recurrence'] && !$master['_instance']) { // compute recurring events until this instance's date if ($recurrence_date = rcube_utils::anytodatetime($instance, $master['start']->getTimezone())) { $recurrence_date->setTime(23, 59, 59); foreach ($this->driver->get_recurring_events($master, $master['start'], $recurrence_date) as $recurring) { if ($recurring['_instance'] == $instance) { // copy attendees block with my partstat to exception $recurring['attendees'] = $event['attendees']; $master['recurrence']['EXCEPTIONS'][] = $recurring; $event = $recurring; // set reference for iTip reply break; } } $master['calendar'] = $event['calendar'] = $calendar['id']; $success = $this->driver->new_event($master); } else { $master = null; } } else { $master = null; } } // save to the selected/default calendar if (!$master) { $event['calendar'] = $calendar['id']; $success = $this->driver->new_event($event); } } else { if ($status == 'declined') { $error_msg = null; } } } } else { if ($status == 'declined' || $dontsave) { $error_msg = null; } else { $error_msg = $this->gettext('nowritecalendarfound'); } } } if ($success) { $message = $event['_method'] == 'REPLY' ? 'attendeupdateesuccess' : ($deleted ? 'successremoval' : ($existing ? 'updatedsuccessfully' : 'importedsuccessfully')); $this->rc->output->command('display_message', $this->gettext(array('name' => $message, 'vars' => array('calendar' => $calendar['name']))), 'confirmation'); } if ($success || $dontsave) { $metadata['calendar'] = $event['calendar']; $metadata['nosave'] = $dontsave; $metadata['rsvp'] = intval($metadata['rsvp']); $metadata['after_action'] = $this->rc->config->get('calendar_itip_after_action', $this->defaults['calendar_itip_after_action']); $this->rc->output->command('plugin.itip_message_processed', $metadata); $error_msg = null; } else { if ($error_msg) { $this->rc->output->command('display_message', $error_msg, 'error'); } } // send iTip reply if ($event['_method'] == 'REQUEST' && $organizer && !$noreply && !in_array(strtolower($organizer['email']), $emails) && !$error_msg) { $event['comment'] = rcube_utils::get_input_value('_comment', rcube_utils::INPUT_POST); $itip = $this->load_itip(); $itip->set_sender_email($reply_sender); if ($itip->send_itip_message($event, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status)) { $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation'); } else { $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error'); } } $this->rc->output->send(); }
/** * Return full data of a specific revision of an event * * @param array Hash array with event properties * @param mixed $rev Revision number * * @return array Event object as hash array * @see calendar_driver::get_event_revison() */ public function get_event_revison($event, $rev, $internal = false) { if (empty($this->bonnie_api)) { return false; } $eventid = $event['id']; $calid = $event['calendar']; list($uid, $mailbox, $msguid) = $this->_resolve_event_identity($event); // call Bonnie API $result = $this->bonnie_api->get('event', $uid, $rev, $mailbox, $msguid); if (is_array($result) && $result['uid'] == $uid && !empty($result['xml'])) { $format = kolab_format::factory('event'); $format->load($result['xml']); $event = $format->to_array(); $format->get_attachments($event, true); // get the right instance from a recurring event if ($eventid != $event['uid']) { $instance_id = substr($eventid, strlen($event['uid']) + 1); // check for recurrence exception first if ($instance = $format->get_instance($instance_id)) { $event = $instance; } else { // not a exception, compute recurrence... $event['_formatobj'] = $format; $recurrence_date = rcube_utils::anytodatetime($instance_id, $event['start']->getTimezone()); foreach ($this->get_recurring_events($event, $event['start'], $recurrence_date) as $instance) { if ($instance['id'] == $eventid) { $event = $instance; break; } } } } if ($format->is_valid()) { $event['calendar'] = $calid; $event['rev'] = $result['rev']; return $internal ? $event : self::to_rcube_event($event); } } return false; }
public static function build_regexp_tests($date_from, $date_to, &$error) { $tests = array(); $dt_from = rcube_utils::anytodatetime($date_from); $dt_to = rcube_utils::anytodatetime($date_to); $interval = $dt_from->diff($dt_to); if ($interval->invert || $interval->days > 365) { $error = 'managesieve.invaliddateformat'; return; } $dt_i = $dt_from; $interval = new DateInterval('P1D'); $matchexp = ''; while (!$dt_i->diff($dt_to)->invert) { $days = (int) $dt_i->format('d'); $matchexp .= $days < 10 ? "[ 0]{$days}" : $days; if ($days == $dt_i->format('t') || $dt_i->diff($dt_to)->days == 0) { $test = array('test' => 'header', 'type' => 'regex', 'arg1' => 'received', 'arg2' => '(' . $matchexp . ') ' . $dt_i->format('M Y')); $tests[] = $test; $matchexp = ''; } else { $matchexp .= '|'; } $dt_i->add($interval); } return $tests; }
/** * Convert a record data set into LDAP field attributes */ private function _map_data($save_cols) { // flatten composite fields first foreach ($this->coltypes as $col => $colprop) { if (is_array($colprop['childs']) && ($values = $this->get_col_values($col, $save_cols, false))) { foreach ($values as $subtype => $childs) { $subtype = $subtype ? ':' . $subtype : ''; foreach ($childs as $i => $child_values) { foreach ((array) $child_values as $childcol => $value) { $save_cols[$childcol . $subtype][$i] = $value; } } } } // if addresses are to be saved as serialized string, do so if (is_array($colprop['serialized'])) { foreach ($colprop['serialized'] as $subtype => $delim) { $key = $col . ':' . $subtype; foreach ((array) $save_cols[$key] as $i => $val) { $values = array($val['street'], $val['locality'], $val['zipcode'], $val['country']); $save_cols[$key][$i] = count(array_filter($values)) ? join($delim, $values) : null; } } } } $ldap_data = array(); foreach ($this->fieldmap as $rf => $fld) { $val = $save_cols[$rf]; // check for value in base field (eg.g email instead of email:foo) list($col, $subtype) = explode(':', $rf); if (!$val && !empty($save_cols[$col])) { $val = $save_cols[$col]; unset($save_cols[$col]); // only use this value once } else { if (!$val && !$subtype) { // extract values from subtype cols $val = $this->get_col_values($col, $save_cols, true); } } if (is_array($val)) { $val = array_filter($val); } // remove empty entries if ($fld && $val) { // The field does exist, add it to the entry. $ldap_data[$fld] = $val; } } foreach ($this->formats as $fld => $format) { if (empty($ldap_data[$fld])) { continue; } switch ($format['type']) { case 'date': if ($dt = rcube_utils::anytodatetime($ldap_data[$fld])) { $ldap_data[$fld] = $dt->format($format['format']); } break; } } return $ldap_data; }
/** * Setter for address record fields * * @param string Field name * @param string Field value * @param string Type/section name */ public function set($field, $value, $type = 'HOME') { $field = strtolower($field); $type_uc = strtoupper($type); switch ($field) { case 'name': case 'displayname': $this->raw['FN'][0][0] = $this->displayname = $value; break; case 'surname': $this->raw['N'][0][0] = $this->surname = $value; break; case 'firstname': $this->raw['N'][0][1] = $this->firstname = $value; break; case 'middlename': $this->raw['N'][0][2] = $this->middlename = $value; break; case 'prefix': $this->raw['N'][0][3] = $value; break; case 'suffix': $this->raw['N'][0][4] = $value; break; case 'nickname': $this->raw['NICKNAME'][0][0] = $this->nickname = $value; break; case 'organization': $this->raw['ORG'][0][0] = $this->organization = $value; break; case 'photo': if (strpos($value, 'http:') === 0) { // TODO: fetch file from URL and save it locally? $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true); } else { $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value)); } break; case 'email': $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc))); $this->email[] = $value; break; case 'im': // save IM subtypes into extension fields $typemap = array_flip($this->immap); if ($field = $typemap[strtolower($type)]) { $this->raw[$field][] = array(0 => $value); } break; case 'birthday': case 'anniversary': if (($val = rcube_utils::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) { $this->raw[$fn][] = array(0 => $val->format('Y-m-d'), 'value' => array('date')); } break; case 'address': if ($this->addresstypemap[$type_uc]) { $type = $this->addresstypemap[$type_uc]; } $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']); // fall through if not empty if (!strlen(join('', $value))) { break; } default: if ($field == 'phone' && $this->phonetypemap[$type_uc]) { $type = $this->phonetypemap[$type_uc]; } if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) { $index = count($this->raw[$tag]); $this->raw[$tag][$index] = (array) $value; if ($type) { $typemap = array_flip($this->typemap); $this->raw[$tag][$index]['type'] = explode(',', $typemap[$type_uc] ? $typemap[$type_uc] : $type); } } else { unset($this->raw[$tag]); } break; } }
/** * Add UI element to copy event invitations or updates to the calendar */ public function mail_messagebody_html($p) { // load iCalendar functions (if necessary) if (!empty($this->ics_parts)) { $this->get_ical(); } $html = ''; foreach ($this->ics_parts as $mime_id) { $part = $this->message->mime_parts[$mime_id]; $charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET; $events = $this->ical->import($this->message->get_part_content($mime_id), $charset); $title = $this->gettext('title'); $date = rcube_utils::anytodatetime($this->message->headers->date); // successfully parsed events? if (empty($events)) { continue; } // show a box for every event in the file foreach ($events as $idx => $event) { // Begin mod by Rosali (Google sends the ics inline and attached -> avoid duplicates with same UID - https://issues.kolab.org/show_bug.cgi?id=3585) $uid = $event['uid'] ? $event['uid'] : md5(serialize($event)); if (isset($this->ics_parts_filtered[$uid])) { continue; } $this->ics_parts_filtered[$uid] = 1; // End mod by Rosali if ($event['_type'] != 'event' && $event['_type'] != 'task') { // skip non-event objects (#2928) // Mod by Rosali (don't skip tasks) continue; } // define buttons according to method if ($this->ical->method == 'REPLY') { $driver = $this->get_default_driver(); $existing = $driver->get_event($event['uid']); $calendar_saveto = new html_hiddenfield(array('class' => 'calendar-saveto', 'value' => $existing['calendar'])); // Mod by Rosali (always pass calendar to GUI) if ($calendar_saveto) { $title = $this->gettext('itipreply'); $buttons = html::tag('input', array('type' => 'button', 'class' => 'button', 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($mime_id . ':' . $idx) . "', this)", 'value' => $this->gettext('updateattendeestatus'))) . $calendar_saveto->show(); } } else { if ($this->ical->method == 'REQUEST') { $emails = $this->get_user_emails(); $title = $event['sequence'] > 0 ? $this->gettext('itipupdate') : $this->gettext('itipinvitation'); // add (hidden) buttons and activate them from asyncronous request foreach (array('accepted', 'tentative', 'declined') as $method) { $rsvp_buttons .= html::tag('input', array('type' => 'button', 'class' => "button {$method}", 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($mime_id . ':' . $idx) . "', this, '{$method}')", 'value' => $this->gettext('itip' . $method))); } $import_button = html::tag('input', array('type' => 'button', 'class' => 'button', 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($mime_id . ':' . $idx) . "', this)", 'value' => $this->gettext('importtocalendar'))); // check my status $status = 'unknown'; foreach ($event['attendees'] as $attendee) { if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) { $status = !empty($attendee['status']) ? strtoupper($attendee['status']) : 'NEEDS-ACTION'; break; } } $dom_id = asciiwords($event['uid'], true); $buttons = html::div(array('id' => 'rsvp-' . $dom_id, 'style' => 'display:none'), $rsvp_buttons); $buttons .= html::div(array('id' => 'import-' . $dom_id, 'style' => 'display:none'), $import_button); $buttons_pre = html::div(array('id' => 'loading-' . $dom_id, 'class' => 'rsvp-status loading'), $this->gettext('loading')); $changed = is_object($event['changed']) ? $event['changed'] : $date; $script = json_serialize(array('uid' => $event['uid'], 'changed' => $changed ? $changed->format('U') : 0, 'sequence' => intval($event['sequence']), 'fallback' => $status)); $this->rc->output->add_script("rcube_calendar.fetch_event_rsvp_status({$script})", 'docready'); } else { if ($this->ical->method == 'CANCEL') { $title = $this->gettext('itipcancellation'); // create buttons to be activated from async request checking existence of this event in local calendars $button_import = html::tag('input', array('type' => 'button', 'class' => 'button', 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($mime_id . ':' . $idx) . "', this)", 'value' => $this->gettext('importtocalendar'))); $button_remove = html::tag('input', array('type' => 'button', 'class' => 'button', 'onclick' => "rcube_calendar.remove_event_from_mail('" . JQ($event['uid']) . "', '" . JQ($event['title']) . "')", 'value' => $this->gettext('removefromcalendar'))); $dom_id = asciiwords($event['uid'], true); $buttons = html::div(array('id' => 'rsvp-' . $dom_id, 'style' => 'display:none'), $button_remove); $buttons .= html::div(array('id' => 'import-' . $dom_id, 'style' => 'display:none'), $button_import); $buttons_pre = html::div(array('id' => 'loading-' . $dom_id, 'class' => 'rsvp-status loading'), $this->gettext('loading')); $changed = is_object($event['changed']) ? $event['changed'] : $date; $script = json_serialize(array('uid' => $event['uid'], 'changed' => $changed ? $changed->format('U') : 0, 'sequence' => intval($event['sequence']), 'fallback' => 'CANCELLED')); $this->rc->output->add_script("rcube_calendar.fetch_event_rsvp_status({$script})", 'docready'); } else { // get a list of writeable calendars // Begin mod by Rosali (https://gitlab.awesome-it.de/kolab/roundcube-plugins/issues/33) $driver = $this->get_default_driver(); $calendars = $driver->list_calendars(false, true); $calendar_select = new html_select(array('name' => 'calendar', 'class' => 'calendar-saveto', 'is_escaped' => true)); // Mod by Rosali (calendar selector can exist multiple times - can't be referenced by ID) $numcals = 0; foreach ($calendars as $calendar) { $driver = $this->get_driver_by_cal($calendar['calendar_id']); if ($driver->readonly !== true) { $calendar_select->add($calendar['name'], $calendar['id']); $numcals++; } } } } } if ($numcals > 0) { $buttons = html::tag('input', array('type' => 'button', 'class' => 'button', 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($mime_id . ':' . $idx) . "', this)", 'value' => $this->gettext('importtocalendar'))) . $calendar_select->show($this->rc->config->get('calendar_default_calendar')); } // show event details with buttons if ($buttons) { $html .= html::div('calendar-invitebox', $this->ui->event_details_table($event, $title) . $buttons_pre . html::div('rsvp-buttons', $buttons)); } // Emd mod by Rosli // limit listing if ($idx >= 3) { break; } } } // prepend event boxes to message body if ($html) { $this->ui->init(); $p['content'] = $html . $p['content']; $this->rc->output->add_label('calendar.savingdata', 'calendar.deleteventconfirm', 'calendar.declinedeleteconfirm'); } return $p; }
/** * Helper method to packs all the given messages into a zip archive * * @param array List of message UIDs to download */ private function _download_messages($messageset) { $rcmail = rcmail::get_instance(); $imap = $rcmail->get_storage(); $mode = rcube_utils::get_input_value('_mode', rcube_utils::INPUT_POST); $temp_dir = $rcmail->config->get('temp_dir'); $tmpfname = tempnam($temp_dir, 'zipdownload'); $tempfiles = array($tmpfname); $folders = count($messageset) > 1; // @TODO: file size limit // open zip file $zip = new ZipArchive(); $zip->open($tmpfname, ZIPARCHIVE::OVERWRITE); if ($mode == 'mbox') { $tmpfp = fopen($tmpfname . '.mbox', 'w'); } foreach ($messageset as $mbox => $uids) { $imap->set_folder($mbox); $path = $folders ? str_replace($imap->get_hierarchy_delimiter(), '/', $mbox) . '/' : ''; if ($uids === '*') { $index = $imap->index($mbox, null, null, true); $uids = $index->get(); } foreach ($uids as $uid) { $headers = $imap->get_message_headers($uid); if ($mode == 'mbox') { // Sender address $from = rcube_mime::decode_address_list($headers->from, null, true, $headers->charset, true); $from = array_shift($from); $from = preg_replace('/\\s/', '-', $from); // Received (internal) date $date = rcube_utils::anytodatetime($headers->internaldate); if ($date) { $date->setTimezone(new DateTimeZone('UTC')); $date = $date->format(self::MBOX_DATE_FORMAT); } // Mbox format header (RFC4155) $header = sprintf("From %s %s\r\n", $from ?: 'MAILER-DAEMON', $date ?: ''); fwrite($tmpfp, $header); // Use stream filter to quote "From " in the message body stream_filter_register('mbox_filter', 'zipdownload_mbox_filter'); $filter = stream_filter_append($tmpfp, 'mbox_filter'); $imap->get_raw_body($uid, $tmpfp); stream_filter_remove($filter); fwrite($tmpfp, "\r\n"); } else { // maildir $subject = rcube_mime::decode_header($headers->subject, $headers->charset); $subject = $this->_filename_from_subject(mb_substr($subject, 0, 16)); $subject = $this->_convert_filename($subject); $disp_name = $path . $uid . ($subject ? " {$subject}" : '') . '.eml'; $tmpfn = tempnam($temp_dir, 'zipmessage'); $tmpfp = fopen($tmpfn, 'w'); $imap->get_raw_body($uid, $tmpfp); $tempfiles[] = $tmpfn; fclose($tmpfp); $zip->addFile($tmpfn, $disp_name); } } } $filename = $folders ? 'messages' : $imap->get_folder(); if ($mode == 'mbox') { $tempfiles[] = $tmpfname . '.mbox'; fclose($tmpfp); $zip->addFile($tmpfname . '.mbox', $filename . '.mbox'); } $zip->close(); $this->_deliver_zipfile($tmpfname, $filename . '.zip'); // delete temporary files from disk foreach ($tempfiles as $tmpfn) { unlink($tmpfn); } exit; }
/** * rcube:utils::anytodatetime() */ function test_anytodatetime() { $test = array('2013-04-22' => '2013-04-22', '2013/04/22' => '2013-04-22', '2013.04.22' => '2013-04-22', '22-04-2013' => '2013-04-22', '22/04/2013' => '2013-04-22', '22.04.2013' => '2013-04-22', '04/22/2013' => '2013-04-22', '22.4.2013' => '2013-04-22', '20130422' => '2013-04-22', '1900-10-10' => '1900-10-10', '01-01-1900' => '1900-01-01', '01/30/1960' => '1960-01-30'); foreach ($test as $datetime => $ts) { $result = rcube_utils::anytodatetime($datetime); $this->assertSame($ts, $result ? $result->format('Y-m-d') : '', "Error parsing date: {$datetime}"); } }
/** * Map task properties for ical exprort using libcalendaring */ public function to_libcal($task) { $object = $task; $object['_type'] = 'task'; $object['categories'] = (array) $task['tags']; // convert to datetime objects if (!empty($task['date'])) { $object['due'] = rcube_utils::anytodatetime($task['date'] . ' ' . $task['time'], $this->timezone); if (empty($task['time'])) { $object['due']->_dateonly = true; } unset($object['date']); } if (!empty($task['startdate'])) { $object['start'] = rcube_utils::anytodatetime($task['startdate'] . ' ' . $task['starttime'], $this->timezone); if (empty($task['starttime'])) { $object['start']->_dateonly = true; } unset($object['startdate']); } $object['complete'] = $task['complete'] * 100; if ($task['complete'] == 1.0 && empty($task['complete'])) { $object['status'] = 'COMPLETED'; } if ($task['flagged']) { $object['priority'] = 1; } else { if (!$task['priority']) { $object['priority'] = 0; } } return $object; }
private function vacation_post() { if (empty($_POST)) { return; } $status = rcube_utils::get_input_value('vacation_status', rcube_utils::INPUT_POST); $subject = rcube_utils::get_input_value('vacation_subject', rcube_utils::INPUT_POST, true); $reason = rcube_utils::get_input_value('vacation_reason', rcube_utils::INPUT_POST, true); $addresses = rcube_utils::get_input_value('vacation_addresses', rcube_utils::INPUT_POST, true); $interval = rcube_utils::get_input_value('vacation_interval', rcube_utils::INPUT_POST); $interval_type = rcube_utils::get_input_value('vacation_interval_type', rcube_utils::INPUT_POST); $date_from = rcube_utils::get_input_value('vacation_datefrom', rcube_utils::INPUT_POST); $date_to = rcube_utils::get_input_value('vacation_dateto', rcube_utils::INPUT_POST); $after = rcube_utils::get_input_value('vacation_after', rcube_utils::INPUT_POST); $interval_type = $interval_type == 'seconds' ? 'seconds' : 'days'; $vacation_action['type'] = 'vacation'; $vacation_action['reason'] = $this->strip_value(str_replace("\r\n", "\n", $reason)); $vacation_action['subject'] = $subject; $vacation_action['addresses'] = $addresses; $vacation_action[$interval_type] = $interval; $vacation_tests = (array) $this->vacation['tests']; foreach ((array) $vacation_action['addresses'] as $aidx => $address) { $vacation_action['addresses'][$aidx] = $address = trim($address); if (empty($address)) { unset($vacation_action['addresses'][$aidx]); } else { if (!rcube_utils::check_email($address)) { $error = 'noemailwarning'; break; } } } if ($vacation_action['reason'] == '') { $error = 'managesieve.emptyvacationbody'; } if ($vacation_action[$interval_type] && !preg_match('/^[0-9]+$/', $vacation_action[$interval_type])) { $error = 'managesieve.forbiddenchars'; } foreach (array('date_from', 'date_to') as $var) { $date = ${$var}; if ($date && ($dt = rcube_utils::anytodatetime($date))) { $type = 'value-' . ($var == 'date_from' ? 'ge' : 'le'); $test = array('test' => 'currentdate', 'part' => 'date', 'type' => $type, 'arg' => $dt->format('Y-m-d')); // find existing date rule foreach ((array) $vacation_tests as $idx => $t) { if ($t['test'] == 'currentdate' && $t['part'] == 'date' && $t['type'] == $type) { $vacation_tests[$idx] = $test; continue 2; } } $vacation_tests[] = $test; } } if (empty($vacation_tests)) { $vacation_tests = $this->rc->config->get('managesieve_vacation_test', array(array('test' => 'true'))); } // @TODO: handle situation when there's no active script if (!$error) { $rule = $this->vacation; $rule['type'] = 'if'; $rule['name'] = $rule['name'] ? $rule['name'] : $this->plugin->gettext('vacation'); $rule['disabled'] = $status == 'off'; $rule['actions'][0] = $vacation_action; $rule['tests'] = $vacation_tests; $rule['join'] = count($vacation_tests) > 1; // reset original vacation rule if (isset($this->vacation['idx'])) { $this->script[$this->vacation['idx']] = null; } // re-order rules if needed if (isset($after) && $after !== '') { // add at target position if ($after >= count($this->script) - 1) { $this->script[] = $rule; } else { $script = array(); foreach ($this->script as $idx => $r) { if ($r) { $script[] = $r; } if ($idx == $after) { $script[] = $rule; } } $this->script = $script; } } else { array_unshift($this->script, $rule); } $this->sieve->script->content = array_values(array_filter($this->script)); if ($this->save_script()) { $this->rc->output->show_message('managesieve.vacationsaved', 'confirmation'); $this->rc->output->send(); } } $this->rc->output->show_message($error ? $error : 'managesieve.saveerror', 'error'); $this->rc->output->send(); }
/** * Convert sql record into a rcube style event object */ private function _read_postprocess($event) { $free_busy_map = array_flip($this->free_busy_map); $sensitivity_map = array_flip($this->sensitivity_map); $event['id'] = $event['event_id']; $event['start'] = new DateTime($event['start']); $event['end'] = new DateTime($event['end']); $event['allday'] = intval($event['all_day']); $event['created'] = new DateTime($event['created']); $event['changed'] = new DateTime($event['changed']); $event['free_busy'] = $free_busy_map[$event['free_busy']]; $event['sensitivity'] = $sensitivity_map[$event['sensitivity']]; $event['calendar'] = $event['calendar_id']; $event['recurrence_id'] = intval($event['recurrence_id']); $event['isexception'] = intval($event['isexception']); // parse recurrence rule if ($event['recurrence'] && preg_match_all('/([A-Z]+)=([^;]+);?/', $event['recurrence'], $m, PREG_SET_ORDER)) { $event['recurrence'] = array(); foreach ($m as $rr) { if (is_numeric($rr[2])) { $rr[2] = intval($rr[2]); } else { if ($rr[1] == 'UNTIL') { $rr[2] = date_create($rr[2]); } else { if ($rr[1] == 'RDATE') { $rr[2] = array_map('date_create', explode(',', $rr[2])); } else { if ($rr[1] == 'EXDATE') { $rr[2] = array_map('date_create', explode(',', $rr[2])); } } } } $event['recurrence'][$rr[1]] = $rr[2]; } } if ($event['recurrence_id']) { libcalendaring::identify_recurrence_instance($event); } if (strlen($event['instance'])) { $event['_instance'] = $event['instance']; if (empty($event['recurrence_id'])) { $event['recurrence_date'] = rcube_utils::anytodatetime($event['_instance'], $event['start']->getTimezone()); } } if ($event['_attachments'] > 0) { $event['attachments'] = (array) $this->list_attachments($event); } // decode serialized event attendees if (strlen($event['attendees'])) { $event['attendees'] = $this->unserialize_attendees($event['attendees']); } else { $event['attendees'] = array(); } // decode serialized alarms if ($event['alarms']) { $event['valarms'] = $this->unserialize_alarms($event['alarms']); } unset($event['event_id'], $event['calendar_id'], $event['notifyat'], $event['all_day'], $event['instance'], $event['_attachments']); return $event; }
/** * Convert the given task record into a data structure that can be passed to kolab_storage backend for saving * (opposite of self::_to_rcube_event()) */ private function _from_rcube_task($task, $old = array()) { $object = $task; $id_prefix = $task['list'] . ':'; if (!empty($task['date'])) { $object['due'] = rcube_utils::anytodatetime($task['date'] . ' ' . $task['time'], $this->plugin->timezone); if (empty($task['time'])) { $object['due']->_dateonly = true; } unset($object['date']); } if (!empty($task['startdate'])) { $object['start'] = rcube_utils::anytodatetime($task['startdate'] . ' ' . $task['starttime'], $this->plugin->timezone); if (empty($task['starttime'])) { $object['start']->_dateonly = true; } unset($object['startdate']); } // as per RFC (and the Kolab schema validation), start and due dates need to be of the same type (#3614) // this should be catched in the client already but just make sure we don't write invalid objects if (!empty($object['start']) && !empty($object['due']) && $object['due']->_dateonly != $object['start']->_dateonly) { $object['start']->_dateonly = true; $object['due']->_dateonly = true; } $object['complete'] = $task['complete'] * 100; if ($task['complete'] == 1.0 && empty($task['complete'])) { $object['status'] = 'COMPLETED'; } if ($task['flagged']) { $object['priority'] = 1; } else { $object['priority'] = $old['priority'] > 1 ? $old['priority'] : 0; } // remove list: prefix from parent_id if (!empty($task['parent_id']) && strpos($task['parent_id'], $id_prefix) === 0) { $object['parent_id'] = substr($task['parent_id'], strlen($id_prefix)); } // copy meta data (starting with _) from old object foreach ((array) $old as $key => $val) { if (!isset($object[$key]) && $key[0] == '_') { $object[$key] = $val; } } // copy recurrence rules if the client didn't submit it (#2713) if (!array_key_exists('recurrence', $object) && $old['recurrence']) { $object['recurrence'] = $old['recurrence']; } // delete existing attachment(s) if (!empty($task['deleted_attachments'])) { foreach ($task['deleted_attachments'] as $attachment) { if (is_array($object['_attachments'])) { foreach ($object['_attachments'] as $idx => $att) { if ($att['id'] == $attachment) { $object['_attachments'][$idx] = false; } } } } unset($task['deleted_attachments']); } // in kolab_storage attachments are indexed by content-id if (is_array($task['attachments'])) { foreach ($task['attachments'] as $idx => $attachment) { $key = null; // Roundcube ID has nothing to do with the storage ID, remove it if ($attachment['content'] || $attachment['path']) { unset($attachment['id']); } else { foreach ((array) $old['_attachments'] as $cid => $oldatt) { if ($oldatt && $attachment['id'] == $oldatt['id']) { $key = $cid; } } } // replace existing entry if ($key) { $object['_attachments'][$key] = $attachment; } else { $object['_attachments'][] = $attachment; } } unset($object['attachments']); } // allow sequence increments if I'm the organizer if ($this->plugin->is_organizer($object) && empty($object['_method'])) { unset($object['sequence']); } else { if (isset($old['sequence']) && empty($object['_method'])) { $object['sequence'] = $old['sequence']; } } unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags'], $object['created']); return $object; }
/** * Single occourrences of recurring events are identified by their RECURRENCE-ID property * in iCal which is represented as 'recurrence_date' in our internal data structure. * * Check if such a property exists and derive the '_instance' identifier and '_savemode' * attributes which are used in the storage backend to identify the nested exception item. */ public static function identify_recurrence_instance(&$object) { // for savemode=all, remove recurrence instance identifiers if (!empty($object['_savemode']) && $object['_savemode'] == 'all' && $object['recurrence']) { unset($object['_instance'], $object['recurrence_date']); } else { if (!empty($object['recurrence_date']) && is_a($object['recurrence_date'], 'DateTime')) { $object['_instance'] = self::recurrence_instance_identifier($object); $object['_savemode'] = $object['thisandfuture'] ? 'future' : 'current'; } else { if (!empty($object['recurrence_id']) && !empty($object['_instance'])) { if (strlen($object['_instance']) > 4) { $object['recurrence_date'] = rcube_utils::anytodatetime($object['_instance'], $object['start']->getTimezone()); } else { $object['recurrence_date'] = clone $object['start']; } } } } }
/** * rcube:utils::anytodatetime() */ function test_anytodatetime_timezone() { $tz = new DateTimeZone('Europe/Helsinki'); $test = array('Jan 1st 2014 +0800' => '2013-12-31 18:00', 'Jan 1st 14 45:42' => '2014-01-01 00:00', 'Jan 1st 2014 UK' => '2014-01-01 00:00', 'Invalid date' => false); foreach ($test as $datetime => $ts) { $result = rcube_utils::anytodatetime($datetime, $tz); if ($result) { $result->setTimezone($tz); } // move to target timezone for comparison $this->assertSame($ts, $result ? $result->format('Y-m-d H:i') : false, "Error parsing date: {$datetime}"); } }
/** * Compare search value with contact data * * @param string $colname Data name * @param string|array $value Data value * @param string $search Search value * @param int $mode Search mode * * @return bool Comparision result */ protected function compare_search_value($colname, $value, $search, $mode) { // The value is a date string, for date we'll // use only strict comparison (mode = 1) // @TODO: partial search, e.g. match only day and month if (in_array($colname, $this->date_cols)) { return ($value = rcube_utils::anytodatetime($value)) && ($search = rcube_utils::anytodatetime($search)) && $value->format('Ymd') == $search->format('Ymd'); } // composite field, e.g. address foreach ((array) $value as $val) { $val = mb_strtolower($val); switch ($mode) { case 1: $got = $val == $search; break; case 2: $got = $search == substr($val, 0, strlen($search)); break; default: $got = strpos($val, $search) !== false; } if ($got) { return true; } } return false; }
/** * Shift dates into user's current timezone * * @param mixed Any kind of a date representation (DateTime object, string or unix timestamp) * @return object DateTime object in user's timezone */ public function adjust_timezone($dt, $dateonly = false) { if (is_numeric($dt)) { $dt = new DateTime('@' . $dt); } else { if (is_string($dt)) { $dt = rcube_utils::anytodatetime($dt); } } if ($dt instanceof DateTime && !($dt->_dateonly || $dateonly)) { $dt->setTimezone($this->timezone); } return $dt; }