public function testParse() { $attendees = Kronolith_Attendee_List::parse('Jürgen Doe <*****@*****.**>, Jane Doe,Jack Doe <*****@*****.**>, jenny@example.com', $this->getMockBuilder('Horde_Notification_Handler')->disableOriginalConstructor()->getMock()); $this->assertInstanceOf('Kronolith_Attendee_List', $attendees); $this->assertEquals(4, count($attendees)); $expectedAttendees = $this->_getAttendees(); foreach ($expectedAttendees as &$attendee) { $attendee->role = Kronolith::PART_REQUIRED; $attendee->response = Kronolith::RESPONSE_NONE; } $attendees = iterator_to_array($attendees); unset($expectedAttendees[5], $expectedAttendees[4]); $this->assertEquals($expectedAttendees, array_values($attendees)); $this->assertEquals(array('email:juergen@example.com', 'name:Jane Doe', 'email:jack@example.com', 'email:jenny@example.com'), array_keys($attendees)); }
protected function _postSave(Kronolith_Event $event) { global $registry; if (!$this->_dav->getInternalObjectId($this->_params['object'], $this->_calendar)) { $this->_dav->addObjectMap($event->id, $this->_params['object'], $this->_calendar); } // Send iTip messages if necessary. $type = Kronolith::ITIP_REQUEST; if ($event->organizer && !Kronolith::isUserEmail($event->creator, $event->organizer)) { $type = Kronolith::ITIP_REPLY; } $event_copy = clone $event; $event_copy->attendees = $event->attendees->without($this->_noItips); $notification = new Horde_Notification_Handler(new Horde_Notification_Storage_Object()); Kronolith::sendITipNotifications($event_copy, $notification, $type); // Send ITIP_CANCEL to any attendee that was removed, but only if this // is the ORGANZIER's copy of the event. if (empty($event->organizer) || $registry->getAuth() == $event->creator && Kronolith::isUserEmail($event->creator, $event->organizer)) { $removed_attendees = new Kronolith_Attendee_List(); foreach ($this->_oldAttendees as $old_attendee) { if (!$event->attendees->has($old_attendee)) { $removed_attendees->add($old_attendee); } } if (count($removed_attendees)) { $cancelEvent = clone $event; Kronolith::sendITipNotifications($cancelEvent, $notification, Kronolith::ITIP_CANCEL, null, null, $removed_attendees); } } }
/** * Save a new or update an existing event from the AJAX event detail view. * * Request parameters used: * - event: The event id. * - cal: The calendar id. * - targetcalendar: If moving events, the targetcalendar to move to. * - as_new: Save an existing event as a new event. * - recur_edit: If editing an instance of a recurring event series, * how to apply the edit [current|future|all]. * - rstart: If editing an instance of a recurring event series, * the original start datetime of this instance. * - rend: If editing an instance of a recurring event series, * the original ending datetime of this instance. * - sendupdates: Should updates be sent to attendees? * - cstart: Start time of the client cache. * - cend: End time of the client cache. */ public function saveEvent() { global $injector, $notification, $registry; $result = $this->_signedResponse($this->vars->targetcalendar); if (!($kronolith_driver = $this->_getDriver($this->vars->targetcalendar))) { return $result; } if ($this->vars->as_new) { unset($this->vars->event); } if (!$this->vars->event) { $perms = $injector->getInstance('Horde_Core_Perms'); if ($perms->hasAppPermission('max_events') !== true && $perms->hasAppPermission('max_events') <= Kronolith::countEvents()) { Horde::permissionDeniedError('kronolith', 'max_events', sprintf(_("You are not allowed to create more than %d events."), $perms->hasAppPermission('max_events'))); return $result; } } if ($this->vars->event && $this->vars->cal && $this->vars->cal != $this->vars->targetcalendar) { if (strpos($kronolith_driver->calendar, '\\')) { list($target, $user) = explode('\\', $kronolith_driver->calendar, 2); } else { $target = $kronolith_driver->calendar; $user = $registry->getAuth(); } $kronolith_driver = $this->_getDriver($this->vars->cal); // Only delete the event from the source calendar if this user has // permissions to do so. try { $sourceShare = Kronolith::getInternalCalendar($kronolith_driver->calendar); $share = Kronolith::getInternalCalendar($target); if ($sourceShare->hasPermission($registry->getAuth(), Horde_Perms::DELETE) && ($user == $registry->getAuth() && $share->hasPermission($registry->getAuth(), Horde_Perms::EDIT) || $user != $registry->getAuth() && $share->hasPermission($registry->getAuth(), Kronolith::PERMS_DELEGATE))) { $kronolith_driver->move($this->vars->event, $target); $kronolith_driver = $this->_getDriver($this->vars->targetcalendar); } } catch (Exception $e) { $notification->push(sprintf(_("There was an error moving the event: %s"), $e->getMessage()), 'horde.error'); return $result; } } if ($this->vars->as_new) { $event = $kronolith_driver->getEvent(); } else { try { // Note that when this is a new event, $this->vars->event will // be empty, so this will create a new event. $event = $kronolith_driver->getEvent($this->vars->event); } catch (Horde_Exception_NotFound $e) { $notification->push(_("The requested event was not found."), 'horde.error'); return $result; } catch (Exception $e) { $notification->push($e); return $result; } } if (!$event->hasPermission(Horde_Perms::EDIT)) { $notification->push(_("You do not have permission to edit this event."), 'horde.warning'); return $result; } $removed_attendees = new Kronolith_Attendee_List(); $old_attendees = new Kronolith_Attendee_List(); if ($this->vars->recur_edit && $this->vars->recur_edit != 'all') { switch ($this->vars->recur_edit) { case 'current': $attributes = new stdClass(); $attributes->rstart = $this->vars->rstart; $attributes->rend = $this->vars->rend; $this->_addException($event, $attributes); // Create a copy of the original event so we can read in the // new form values for the exception. We also MUST reset the // recurrence property even though we won't be using it, since // clone() does not do a deep copy. Otherwise, the original // event's recurrence will become corrupt. $newEvent = clone $event; $newEvent->recurrence = new Horde_Date_Recurrence($event->start); $newEvent->readForm($event); // Create an exception event from the new properties. $exception = $this->_copyEvent($event, $newEvent, $attributes); $exception->start = $newEvent->start; $exception->end = $newEvent->end; // Save the new exception. $attributes->cstart = $this->vars->cstart; $attributes->cend = $this->vars->cend; $result = $this->_saveEvent($exception, $event, $attributes); break; case 'future': $instance = new Horde_Date($this->vars->rstart, $event->timezone); $exception = clone $instance; $exception->mday--; if ($event->end->compareDate($exception) > 0) { // Same as 'all' since this is the first recurrence. $this->vars->recur_edit = 'all'; return $this->saveEvent(); } else { $event->recurrence->setRecurEnd($exception); $newEvent = $kronolith_driver->getEvent(); $newEvent->readForm(); $newEvent->uid = null; $result = $this->_saveEvent($newEvent, $event, $this->vars, true); } } } else { $old_start = !empty($event->start) ? clone $event->start : false; $old_end = !empty($event->end) ? clone $event->end : false; $old_recurrence = !empty($event->recurrence) ? clone $event->recurrence : false; try { $old_attendees = $event->attendees; $event->readForm(); foreach ($old_attendees as $old_attendee) { if (!$event->attendees->has($old_attendee)) { $removed_attendees->add($old_attendee); } } if (!empty($old_start) && !empty($old_end) && $event->recurs() && ($old_start->compareTime($event->start) !== 0 || $old_end->compareTime($event->end) !== 0) || $old_recurrence && !$event->recurrence->isEqual($old_recurrence)) { // Disconnect any existing exceptions when the // start/end time changes still @todo this when the // recurrence series type/properties change too. $event->disconnectExceptions(); } $result = $this->_saveEvent($event); } catch (Exception $e) { $notification->push($e); return $result; } } if ($this->vars->sendupdates) { if ($this->vars->attendance) { Kronolith::sendITipNotifications($event, $notification, Kronolith::ITIP_REPLY); } // Only the ORGANIZER's copy should trigger a REQUEST or CANCEL. if (empty($event->organizer)) { $type = $event->status == Kronolith::STATUS_CANCELLED ? Kronolith::ITIP_CANCEL : Kronolith::ITIP_REQUEST; Kronolith::sendITipNotifications($event, $notification, $type); } } // Send a CANCEL iTip for attendees that have been removed, but only if // the entire event isn't being marked as cancelled (which would be // caught above). if (empty($event->organizer) && count($removed_attendees)) { $cancelEvent = clone $event; Kronolith::sendITipNotifications($cancelEvent, $notification, Kronolith::ITIP_CANCEL, null, null, $removed_attendees); } Kronolith::notifyOfResourceRejection($event); return $result; }
/** * Copyright 2004-2007 Code Fusion <http://www.codefusion.co.za/> * Copyright 2004-2007 Stuart Binge <*****@*****.**> * * See the enclosed file COPYING for license information (GPL). If you * did not receive this file, see http://www.horde.org/licenses/gpl. */ require_once __DIR__ . '/lib/Application.php'; Horde_Registry::appInit('kronolith'); if (Kronolith::showAjaxView()) { Horde::url('', true)->redirect(); } // Get the current attendees array from the session cache. $attendees = $session->get('kronolith', 'attendees'); if (!$attendees) { $attendees = new Kronolith_Attendee_List(); } $resources = $session->get('kronolith', 'resources', Horde_Session::TYPE_ARRAY); $editAttendee = null; // Get the current Free/Busy view; default to the 'day' view if none specified. $view = Horde_Util::getFormData('view', 'Day'); // Get the date information. $start = new Horde_Date(Horde_Util::getFormData('startdate'), date_default_timezone_get()); switch ($view) { case 'Day': $end = clone $start; $end->mday++; break; case 'Workweek': case 'Week': $diff = $start->dayOfWeek() - ($view == 'Workweek' ? 1 : $prefs->getValue('week_start_monday'));
/** * Reads form/post data and updates this event's properties. * * @param Kronolith_Event|null $existing If this is an exception event * this is taken as the base event. * @since 4.2.6 * */ public function readForm(Kronolith_Event $existing = null) { global $notification, $prefs, $registry, $session; // Event owner. $targetcalendar = Horde_Util::getFormData('targetcalendar'); if (strpos($targetcalendar, '\\')) { list(, $this->creator) = explode('\\', $targetcalendar, 2); } elseif (!isset($this->_id)) { $this->creator = $registry->getAuth(); } // Basic fields. $this->title = Horde_Util::getFormData('title', $this->title); $this->description = Horde_Util::getFormData('description', $this->description); $this->location = Horde_Util::getFormData('location', $this->location); $this->timezone = Horde_Util::getFormData('timezone', $this->timezone); $this->private = (bool) Horde_Util::getFormData('private'); // if the field is empty you are the organizer (and so organizer should be null) $this->organizer = Horde_Util::getFormData('organizer', $this->organizer) ?: null; // URL. $url = Horde_Util::getFormData('eventurl', $this->url); if (strlen($url)) { // Analyze and re-construct. $url = @parse_url($url); if ($url) { if (function_exists('http_build_url')) { if (empty($url['path'])) { $url['path'] = '/'; } $url = http_build_url($url); } else { $new_url = ''; if (isset($url['scheme'])) { $new_url .= $url['scheme'] . '://'; } if (isset($url['user'])) { $new_url .= $url['user']; if (isset($url['pass'])) { $new_url .= ':' . $url['pass']; } $new_url .= '@'; } if (isset($url['host'])) { // Convert IDN hosts to ASCII. if (function_exists('idn_to_ascii')) { $url['host'] = @idn_to_ascii($url['host']); } elseif (Horde_Mime::is8bit($url['host'])) { //throw new Kronolith_Exception(_("Invalid character in URL.")); $url['host'] = ''; } $new_url .= $url['host']; } if (isset($url['path'])) { $new_url .= $url['path']; } if (isset($url['query'])) { $new_url .= '?' . $url['query']; } if (isset($url['fragment'])) { $new_url .= '#' . $url['fragment']; } $url = $new_url; } } } $this->url = $url; // Status. $this->status = Horde_Util::getFormData('status', $this->status); // Attendees. $attendees = $session->get('kronolith', 'attendees'); if (!$attendees) { $attendees = new Kronolith_Attendee_List(); } $newattendees = Horde_Util::getFormData('attendees'); $userattendees = Horde_Util::getFormData('users'); if (!is_null($newattendees) || !is_null($userattendees)) { if ($newattendees) { $newattendees = Kronolith_Attendee_List::parse(trim($newattendees), $notification); } else { $newattendees = new Kronolith_Attendee_List(); } if ($userattendees) { foreach (explode(',', $userattendees) as $user) { if (!($newUser = Kronolith::validateUserAttendee($user))) { $notification->push(sprintf(_("The user \"%s\" does not exist."), $newUser), 'horde.error'); } else { $newattendees->add($newUser); } } } // First add new attendees missing in the current list. foreach ($newattendees as $attendee) { if (!$attendees->has($attendee)) { $attendees->add($attendee); } } // Now check for attendees in the current list that don't exist in // the new attendee list anymore. $finalAttendees = new Kronolith_Attendee_List(); foreach ($attendees as $attendee) { if (!$newattendees->has($attendee)) { continue; } if (Kronolith::isUserEmail($this->creator, $attendee->email)) { $attendee->response = Horde_Util::getFormData('attendance'); } $finalAttendees->add($attendee); } $attendees = $finalAttendees; } $this->attendees = $attendees; // Event start. $allDay = Horde_Util::getFormData('whole_day'); if ($start_date = Horde_Util::getFormData('start_date')) { // From ajax interface. $this->start = Kronolith::parseDate($start_date . ' ' . Horde_Util::getFormData('start_time'), true, $this->timezone); if ($allDay) { $this->start->hour = $this->start->min = $this->start->sec = 0; } } elseif ($start = Horde_Util::getFormData('start')) { // From traditional interface. $start_year = $start['year']; $start_month = $start['month']; $start_day = $start['day']; $start_hour = Horde_Util::getFormData('start_hour'); $start_min = Horde_Util::getFormData('start_min'); $am_pm = Horde_Util::getFormData('am_pm'); if (!$prefs->getValue('twentyFour')) { if ($am_pm == 'PM') { if ($start_hour != 12) { $start_hour += 12; } } elseif ($start_hour == 12) { $start_hour = 0; } } if (Horde_Util::getFormData('end_or_dur') == 1) { if ($allDay) { $start_hour = 0; $start_min = 0; $dur_day = 0; $dur_hour = 24; $dur_min = 0; } else { $dur_day = (int) Horde_Util::getFormData('dur_day'); $dur_hour = (int) Horde_Util::getFormData('dur_hour'); $dur_min = (int) Horde_Util::getFormData('dur_min'); } } $this->start = new Horde_Date(array('hour' => $start_hour, 'min' => $start_min, 'month' => $start_month, 'mday' => $start_day, 'year' => $start_year), $this->timezone); } // Event end. if ($end_date = Horde_Util::getFormData('end_date')) { // From ajax interface. $this->end = Kronolith::parseDate($end_date . ' ' . Horde_Util::getFormData('end_time'), true, $this->timezone); if ($allDay) { $this->end->hour = $this->end->min = $this->end->sec = 0; $this->end->mday++; } } elseif (Horde_Util::getFormData('end_or_dur') == 1) { // Event duration from traditional interface. $this->end = new Horde_Date(array('hour' => $start_hour + $dur_hour, 'min' => $start_min + $dur_min, 'month' => $start_month, 'mday' => $start_day + $dur_day, 'year' => $start_year)); } elseif ($end = Horde_Util::getFormData('end')) { // From traditional interface. $end_year = $end['year']; $end_month = $end['month']; $end_day = $end['day']; $end_hour = Horde_Util::getFormData('end_hour'); $end_min = Horde_Util::getFormData('end_min'); $end_am_pm = Horde_Util::getFormData('end_am_pm'); if (!$prefs->getValue('twentyFour')) { if ($end_am_pm == 'PM') { if ($end_hour != 12) { $end_hour += 12; } } elseif ($end_hour == 12) { $end_hour = 0; } } $this->end = new Horde_Date(array('hour' => $end_hour, 'min' => $end_min, 'month' => $end_month, 'mday' => $end_day, 'year' => $end_year), $this->timezone); if ($this->end->compareDateTime($this->start) < 0) { $this->end = new Horde_Date($this->start); } } $this->allday = false; // Alarm. if (!is_null($alarm = Horde_Util::getFormData('alarm'))) { if ($alarm) { $value = Horde_Util::getFormData('alarm_value'); $unit = Horde_Util::getFormData('alarm_unit'); if ($value == 0) { $value = $unit = 1; } $this->alarm = $value * $unit; // Notification. if (Horde_Util::getFormData('alarm_change_method')) { $types = Horde_Util::getFormData('event_alarms'); $methods = array(); if (!empty($types)) { foreach ($types as $type) { $methods[$type] = array(); switch ($type) { case 'notify': $methods[$type]['sound'] = Horde_Util::getFormData('event_alarms_sound'); break; case 'mail': $methods[$type]['email'] = Horde_Util::getFormData('event_alarms_email'); break; case 'popup': break; } } } $this->methods = $methods; } else { $this->methods = array(); } } else { $this->alarm = 0; $this->methods = array(); } } // Recurrence. $this->recurrence = $this->readRecurrenceForm($this->start, $this->timezone, $this->recurrence); // Convert to local timezone. $this->setTimezone(false); $this->_handleResources($existing); // Tags. $this->tags = Horde_Util::getFormData('tags', $this->tags); // Geolocation if (Horde_Util::getFormData('lat') && Horde_Util::getFormData('lon')) { $this->geoLocation = array('lat' => Horde_Util::getFormData('lat'), 'lon' => Horde_Util::getFormData('lon'), 'zoom' => Horde_Util::getFormData('zoom')); } $this->initialized = true; }