* @author Jan Schneider * @author Gonçalo Queirós * @category Horde * @license http://www.horde.org/licenses/gpl GPL * @package Kronolith */ class Kronolith_Ajax_Application_Handler extends Horde_Core_Ajax_Application_Handler { protected $_external = array('embed'); /** * Just polls for alarm messages and keeps session fresh for now. */ public function poll() { return false; } /** * Returns a list of all calendars. */ public function listCalendars() { Kronolith::initialize(); $all_external_calendars = $GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS); $result = new stdClass; $auth_name = $GLOBALS['registry']->getAuth(); // Calendars. Do some twisting to sort own calendar before shared // calendars. foreach (array(true, false) as $my) { foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS) as $id => $calendar) { $owner = ($auth_name && ($calendar->owner() == $auth_name)); if (($my && $owner) || (!$my && !$owner)) { $result->calendars['internal'][$id] = $calendar->toHash(); } } // Tasklists if (Kronolith::hasApiPermission('tasks')) { foreach ($GLOBALS['registry']->tasks->listTasklists($my, Horde_Perms::SHOW, false) as $id => $tasklist) { if (isset($all_external_calendars['tasks/' . $id])) { $owner = ($auth_name && ($tasklist->get('owner') == $auth_name)); if (($my && $owner) || (!$my && !$owner)) { $result->calendars['tasklists']['tasks/' . $id] = $all_external_calendars['tasks/' . $id]->toHash(); } } } } } // Resources if (!empty($GLOBALS['conf']['resource']['driver'])) { foreach (Kronolith::getDriver('Resource')->listResources() as $resource) { if ($resource->get('type') != Kronolith_Resource::TYPE_GROUP) { $rcal = new Kronolith_Calendar_Resource(array( 'resource' => $resource )); $result->calendars['resource'][$resource->get('calendar')] = $rcal->toHash(); } else { $rcal = new Kronolith_Calendar_ResourceGroup(array( 'resource' => $resource )); $result->calendars['resourcegroup'][$resource->getId()] = $rcal->toHash(); } } } // Timeobjects foreach ($all_external_calendars as $id => $calendar) { if ($calendar->api() != 'tasks' && $calendar->display()) { $result->calendars['external'][$id] = $calendar->toHash(); } } // Remote calendars foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_REMOTE_CALENDARS) as $url => $calendar) { $result->calendars['remote'][$url] = $calendar->toHash(); } // Holidays foreach ($GLOBALS['calendar_manager']->get(Kronolith::ALL_HOLIDAYS) as $id => $calendar) { $result->calendars['holiday'][$id] = $calendar->toHash(); } return $result; } /** * TODO */ public function listEvents() { global $session; $start = new Horde_Date($this->vars->start); $end = new Horde_Date($this->vars->end); $result = $this->_signedResponse($this->vars->cal); if (!($kronolith_driver = $this->_getDriver($this->vars->cal))) { return $result; } try { $session->close(); $events = $kronolith_driver->listEvents($start, $end, array( 'show_recurrence' => true, 'json' => true) ); $session->start(); if (count($events)) { $result->events = $events; } } catch (Exception $e) { $session->start(); $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * Returns a JSON object representing the requested event. * * Request variables used: * - cal: The calendar id * - id: The event id * - date: The date of the event we are requesting [OPTIONAL] * - rsd: The event start date of the instance of a recurring event, if * requesting a specific instance. * - red: The event end date of the instance of a recurring event, if * requesting a specific instance. */ public function getEvent() { $result = new stdClass; if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) || !isset($this->vars->id)) { return $result; } try { $event = $kronolith_driver->getEvent($this->vars->id, $this->vars->date); $event->setTimezone(true); $result->event = $event->toJson(null, true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'); // If recurring, we need to format the dates of this instance, since // Kronolith_Driver#getEvent will return the start/end dates of the // original event in the series. if ($event->recurs() && $this->vars->rsd) { $rs = new Horde_Date($this->vars->rsd); $result->event->rsd = $rs->strftime('%x'); $re = new Horde_Date($this->vars->red); $result->event->red = $re->strftime('%x'); } } catch (Horde_Exception_NotFound $e) { $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error'); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * 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 { $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 = $old_attendees = array(); 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 { try { $old_attendees = $event->attendees; $event->readForm(); $removed_attendees = array_diff( array_keys($old_attendees), array_keys($event->attendees) ); $result = $this->_saveEvent($event); } catch (Exception $e) { $notification->push($e); return $result; } } if (($result !== true) && $this->vars->sendupdates) { $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($removed_attendees)) { $to_cancel = array(); foreach ($removed_attendees as $email) { $to_cancel[$email] = $old_attendees[$email]; } $cancelEvent = clone $event; Kronolith::sendITipNotifications( $cancelEvent, $notification, Kronolith::ITIP_CANCEL, null, null, $to_cancel ); } Kronolith::notifyOfResourceRejection($event); return $result; } /** * TODO */ public function quickSaveEvent() { $cal = explode('|', $this->vars->cal, 2); try { $event = Kronolith::quickAdd($this->vars->text, $cal[1]); return $this->_saveEvent($event); } catch (Horde_Exception $e) { $GLOBALS['notification']->push($e); $result = $this->_signedResponse($this->vars->cal); $result->error = true; return $result; } } /** * Update event details as a result of a drag/drop operation (which would * only affect the event's start/end times). * * Uses the following request variables: *
     *   -cal:  The calendar id.
     *   -id:   The event id.
     *   -att:  Attribute hash of changed values. Can contain:
     *      -start:     A new start datetime for the event.
     *      -end:       A new end datetime for the event.
     *      -offDays:   An offset of days to apply to the event.
     *      -offMins:   An offset of minutes to apply to the event.
     *      -rstart:    The orginal start datetime of a series instance.
     *      -rend:      The original end datetime of a series instance.
     *      -rday:      A new start value for a series instance (used when
     *                  dragging on the month view where only the date can
     *                  change, and not the start/end times).
     *      -u:         Send update to attendees.
     *
*/ public function updateEvent() { $result = $this->_signedResponse($this->vars->cal); if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) || !isset($this->vars->id)) { return $result; } try { $oevent = $kronolith_driver->getEvent($this->vars->id); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } if (!$oevent) { $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error'); return $result; } elseif (!$oevent->hasPermission(Horde_Perms::EDIT)) { $GLOBALS['notification']->push(_("You do not have permission to edit this event."), 'horde.warning'); return $result; } $attributes = Horde_Serialize::unserialize($this->vars->att, Horde_Serialize::JSON); // If this is a recurring event, need to create an exception. if ($oevent->recurs()) { $this->_addException($oevent, $attributes); $event = $this->_copyEvent($oevent, null, $attributes); } else { $event = clone($oevent); } foreach ($attributes as $attribute => $value) { switch ($attribute) { case 'start': $newDate = new Horde_Date($value); $newDate->setTimezone($event->start->timezone); $event->start = clone($newDate); break; case 'end': $newDate = new Horde_Date($value); $newDate->setTimezone($event->end->timezone); $event->end = clone($newDate); if ($event->end->hour == 23 && $event->end->min == 59 && $event->end->sec == 59) { $event->end->mday++; $event->end->hour = $event->end->min = $event->end->sec = 0; } break; case 'offDays': $event->start->mday += $value; $event->end->mday += $value; break; case 'offMins': $event->start->min += $value; $event->end->min += $value; break; } } $result = $this->_saveEvent($event, ($oevent->recurs() ? $oevent : null), $attributes); if ($this->vars->u) { Kronolith::sendITipNotifications($event, $GLOBALS['notification'], Kronolith::ITIP_REQUEST); } return $result; } /** * Deletes an event, or an instance of an event series from the backend. * * Uses the following request variables: *
     *   - cal:          The calendar id.
     *   - id:           The event id.
     *   - r:            If this is an event series, what type of deletion to
     *                   perform [future | current | all].
     *   - rstart:       The start time of the event instance being removed, if
     *                   this is a series instance.
     *   - cstart:       The start date of the client event cache.
     *   - cend:         The end date of the client event cache.
     *   - sendupdates:  Send cancellation notice to attendees?
     * 
*/ public function deleteEvent() { $result = new stdClass; $instance = null; if (!($kronolith_driver = $this->_getDriver($this->vars->cal)) || !isset($this->vars->id)) { return $result; } try { $event = $kronolith_driver->getEvent($this->vars->id); if (!$event->hasPermission(Horde_Perms::DELETE)) { $GLOBALS['notification']->push(_("You do not have permission to delete this event."), 'horde.warning'); return $result; } $range = null; if ($event->recurs() && $this->vars->r != 'all') { switch ($this->vars->r) { case 'future': // Deleting all future instances. // @TODO: Check if we need to find future exceptions // that are after $recurEnd and remove those as well. $instance = new Horde_Date($this->vars->rstart, $event->timezone); $recurEnd = clone($instance); $recurEnd->hour = 0; $recurEnd->min = 0; $recurEnd->sec = 0; $recurEnd->mday--; if ($event->end->compareDate($recurEnd) > 0) { $kronolith_driver->deleteEvent($event->id); $result = $this->_signedResponse($this->vars->cal); $result->events = array(); } else { $event->recurrence->setRecurEnd($recurEnd); $result = $this->_saveEvent($event, $event, $this->vars); } $range = Kronolith::RANGE_THISANDFUTURE; break; case 'current': // Deleting only the current instance. $instance = new Horde_Date($this->vars->rstart, $event->timezone); $event->recurrence->addException( $instance->year, $instance->month, $instance->mday); $result = $this->_saveEvent($event, $event, $this->vars); } } else { // Deleting an entire series, or this is a single event only. $kronolith_driver->deleteEvent($event->id); $result = $this->_signedResponse($this->vars->cal); $result->events = array(); $result->uid = $event->uid; } if ($this->vars->sendupdates) { Kronolith::sendITipNotifications( $event, $GLOBALS['notification'], Kronolith::ITIP_CANCEL, $instance, $range); } $result->deleted = true; } catch (Horde_Exception_NotFound $e) { $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error'); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * TODO */ public function searchEvents() { $query = Horde_Serialize::unserialize($this->vars->query, Horde_Serialize::JSON); if (!isset($query->start)) { $query->start = new Horde_Date($_SERVER['REQUEST_TIME']); } if (!isset($query->end)) { $query->end = null; } switch ($this->vars->time) { case 'all': $query->start = null; $query->end = null; break; case 'future': $query->start = new Horde_Date($_SERVER['REQUEST_TIME']); $query->end = null; break; case 'past': $query->start = null; $query->end = new Horde_Date($_SERVER['REQUEST_TIME']); break; } $tagger = new Kronolith_Tagger(); $cals = Horde_Serialize::unserialize($this->vars->cals, Horde_Serialize::JSON); $events = array(); foreach ($cals as $cal) { if (!($kronolith_driver = $this->_getDriver($cal))) { continue; } try { $result = $kronolith_driver->search($query, true); if ($result) { $events[$cal] = $result; } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } $split = explode('|', $cal); if ($split[0] == 'internal') { $result = $tagger->search($query->title, array('type' => 'event', 'calendar' => $split[1])); foreach ($result['events'] as $uid) { Kronolith::addSearchEvents($events[$cal], $kronolith_driver->getByUID($uid), $query, true); } } } $result = new stdClass; $result->view = 'search'; $result->query = $this->vars->query; if ($events) { $result->events = $events; } return $result; } /** * TODO */ public function listTasks() { if (!$GLOBALS['registry']->hasMethod('tasks/listTasks')) { return false; } $result = new stdClass; $result->list = $this->vars->list; $result->type = $this->vars->type; try { $tasks = $GLOBALS['registry']->tasks ->listTasks(array( 'tasklists' => $this->vars->list, 'completed' => $this->vars->type == 'incomplete' ? 'future_incomplete' : $this->vars->type, 'include_tags' => true, 'external' => false, 'json' => true )); if (count($tasks)) { $result->tasks = $tasks; } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * TODO */ public function getTask() { if (!$GLOBALS['registry']->hasMethod('tasks/getTask') || !isset($this->vars->id) || !isset($this->vars->list)) { return false; } $result = new stdClass; try { $task = $GLOBALS['registry']->tasks->getTask($this->vars->list, $this->vars->id); if ($task) { $result->task = $task->toJson(true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'); } else { $GLOBALS['notification']->push(_("The requested task was not found."), 'horde.error'); } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * TODO */ public function saveTask() { if (!$GLOBALS['registry']->hasMethod('tasks/updateTask') || !$GLOBALS['registry']->hasMethod('tasks/addTask')) { return false; } $id = $this->vars->task_id; $list = $this->vars->old_tasklist; $task = $this->vars->task; $result = $this->_signedResponse('tasklists|tasks/' . $task['tasklist']); $due = trim($task['due_date'] . ' ' . $task['due_time']); if (!empty($due)) { try { $due = Kronolith::parseDate($due); $task['due'] = $due->timestamp(); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } } if ($task['alarm']['on']) { $value = $task['alarm']['value']; $unit = $task['alarm']['unit']; if ($value == 0) { $value = $unit = 1; } $task['alarm'] = $value * $unit; if (isset($task['alarm_methods']) && isset($task['methods'])) { foreach (array_keys($task['methods']) as $method) { if (!in_array($method, $task['alarm_methods'])) { unset($task['methods'][$method]); } } foreach ($task['alarm_methods'] as $method) { if (!isset($task['methods'][$method])) { $task['methods'][$method] = array(); } } } else { $task['methods'] = array(); } } else { $task['alarm'] = 0; $task['methods'] = array(); } unset($task['alarm_methods']); if (!isset($task['completed'])) { $task['completed'] = false; } if ($this->vars->recur && !empty($due)) { $task['recurrence'] = Kronolith_Event::readRecurrenceForm($due, 'UTC'); } $task['tags'] = Horde_Util::getFormData('tags'); try { $ids = ($id && $list) ? $GLOBALS['registry']->tasks->updateTask($list, $id, $task) : $GLOBALS['registry']->tasks->addTask($task); if (!$id) { $id = $ids[0]; } $task = $GLOBALS['registry']->tasks->getTask($task['tasklist'], $id); $result->tasks = array($id => $task->toJson(false, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A')); $result->type = $task->completed ? 'complete' : 'incomplete'; $result->list = $task->tasklist; } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } if ($due && $kronolith_driver = $this->_getDriver('tasklists|tasks/' . $task->tasklist)) { try { $event = $kronolith_driver->getEvent('_tasks' . $id); $end = clone $due; $end->hour = 23; $end->min = $end->sec = 59; $start = clone $due; $start->hour = $start->min = $start->sec = 0; $events = array(); Kronolith::addEvents($events, $event, $start, $end, true, true); if (count($events)) { $result->events = $events; } } catch (Horde_Exception_NotFound $e) { } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } } return $result; } /** * TODO */ public function quickSaveTask() { if (!$GLOBALS['registry']->hasMethod('tasks/quickAdd')) { return false; } $result = $this->_signedResponse( 'tasklists|tasks/' . $this->vars->tasklist); try { $ids = $GLOBALS['registry']->tasks->quickAdd($this->vars->text); $result->type = 'incomplete'; $result->list = $this->vars->tasklist; $result->tasks = array(); foreach ($ids as $uid) { $task = $GLOBALS['registry']->tasks->export($uid, 'raw'); $result->tasks[$task->id] = $task->toJson( false, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A' ); } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * TODO */ public function deleteTask() { $result = new stdClass; if (!$GLOBALS['registry']->hasMethod('tasks/deleteTask') || !isset($this->vars->id) || !isset($this->vars->list)) { return $result; } try { $GLOBALS['registry']->tasks->deleteTask($this->vars->list, $this->vars->id); $result->deleted = true; } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * TODO */ public function toggleCompletion() { $result = new stdClass; if (!$GLOBALS['registry']->hasMethod('tasks/toggleCompletion')) { return $result; } try { $result->toggled = $GLOBALS['registry']->tasks->toggleCompletion($this->vars->id, $this->vars->list); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * Generate a list of most frequently used tags for the current user. */ public function listTopTags() { $tagger = new Kronolith_Tagger(); $result = new stdClass; $result->tags = array(); $tags = $tagger->getCloud($GLOBALS['registry']->getAuth(), 10, true); foreach ($tags as $tag) { $result->tags[] = $tag['tag_name']; } return $result; } /** * Return fb information for the requested attendee or resource. * * Uses the following request parameters: *
     *  -email:    The attendee's email address.
     *  -resource: The resource id.
     *
*/ public function getFreeBusy() { $result = new stdClass; if ($this->vars->email) { try { $result->fb = Kronolith_FreeBusy::get($this->vars->email, true); } catch (Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.warning'); } } elseif ($this->vars->resource) { try { $resource = Kronolith::getDriver('Resource') ->getResource($this->vars->resource); try { $result->fb = $resource->getFreeBusy(null, null, true, true); } catch (Horde_Exception $e) { // Resource groups can't provide FB information. $result->fb = null; } } catch (Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.warning'); } } return $result; } /** * TODO */ public function searchCalendars() { $result = new stdClass; $result->events = 'Searched for calendars: ' . $this->vars->title; return $result; } /** * TODO */ public function saveCalendar() { $calendar_id = $this->vars->calendar; $result = new stdClass; switch ($this->vars->type) { case 'internal': $info = array(); foreach (array('name', 'color', 'description', 'tags') as $key) { $info[$key] = $this->vars->$key; } // Create a calendar. if (!$calendar_id) { if (!$GLOBALS['registry']->getAuth() || $GLOBALS['prefs']->isLocked('default_share')) { return $result; } try { $calendar = Kronolith::addShare($info); Kronolith::readPermsForm($calendar); if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { $wrapper = new Kronolith_Calendar_Internal(array('share' => $calendar)); $result->saved = true; $result->id = $calendar->getName(); $result->calendar = $wrapper->toHash(); } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success'); break; } // Update a calendar. try { $calendar = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar_id); $original_name = $calendar->get('name'); $original_owner = $calendar->get('owner'); Kronolith::updateShare($calendar, $info); Kronolith::readPermsForm($calendar); if ($calendar->get('owner') != $original_owner) { $result->deleted = true; } if ($calendar->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { $wrapper = new Kronolith_Calendar_Internal(array('share' => $calendar)); $result->saved = true; $result->calendar = $wrapper->toHash(); } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } if ($calendar->get('name') != $original_name) { $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success'); } else { $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success'); } break; case 'tasklists': $calendar = array(); foreach (array('name', 'color', 'description') as $key) { $calendar[$key] = $this->vars->$key; } // Create a task list. if (!$calendar_id) { if (!$GLOBALS['registry']->getAuth() || $GLOBALS['prefs']->isLocked('default_share')) { return $result; } try { $tasklistId = $GLOBALS['registry']->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']); $tasklists = $GLOBALS['registry']->tasks->listTasklists(true); if (!isset($tasklists[$tasklistId])) { $GLOBALS['notification']->push(_("Added task list not found."), 'horde.error'); return $result; } $tasklist = $tasklists[$tasklistId]; Kronolith::readPermsForm($tasklist); if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { $wrapper = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $tasklistId, 'share' => $tasklist)); // Update external calendars caches. $all_external = $GLOBALS['session']->get('kronolith', 'all_external_calendars'); $all_external[] = array('a' => 'tasks', 'n' => $tasklistId, 'd' => $tasklist->get('name')); $GLOBALS['session']->set('kronolith', 'all_external_calendars', $all_external); $display_external = $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_EXTERNAL_CALENDARS); $display_external[] = 'tasks/' . $tasklistId; $GLOBALS['calendar_manager']->set(Kronolith::DISPLAY_EXTERNAL_CALENDARS, $display_external); $GLOBALS['prefs']->setValue('display_external_cals', serialize($display_external)); $all_external = $GLOBALS['calendar_manager']->get(Kronolith::ALL_EXTERNAL_CALENDARS); $all_external['tasks/' . $tasklistId] = $wrapper; $GLOBALS['calendar_manager']->set(Kronolith::ALL_EXTERNAL_CALENDARS, $all_external); $result->saved = true; $result->id = 'tasks/' . $tasklistId; $result->calendar = $wrapper->toHash(); } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been created."), $calendar['name']), 'horde.success'); break; } // Update a task list. $calendar_id = substr($calendar_id, 6); try { $GLOBALS['registry']->tasks->updateTasklist($calendar_id, $calendar); $tasklists = $GLOBALS['registry']->tasks->listTasklists(true, Horde_Perms::EDIT); $tasklist = $tasklists[$calendar_id]; $original_owner = $tasklist->get('owner'); Kronolith::readPermsForm($tasklist); if ($tasklist->get('owner') != $original_owner) { $result->deleted = true; } if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::SHOW)) { $wrapper = new Kronolith_Calendar_External_Tasks(array('api' => 'tasks', 'name' => $calendar_id, 'share' => $tasklist)); $result->saved = true; $result->calendar = $wrapper->toHash(); } } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } if ($tasklist->get('name') != $calendar['name']) { $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been renamed to \"%s\"."), $tasklist->get('name'), $calendar['name']), 'horde.success'); } else { $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been saved."), $tasklist->get('name')), 'horde.success'); } break; case 'remote': $calendar = array(); foreach (array('name', 'desc', 'url', 'color', 'user', 'password') as $key) { $calendar[$key] = $this->vars->$key; } try { Kronolith::subscribeRemoteCalendar($calendar, $calendar_id); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } if ($calendar_id) { $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $calendar['name']), 'horde.success'); } else { $GLOBALS['notification']->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success'); $result->id = $calendar['url']; } $wrapper = new Kronolith_Calendar_Remote($calendar); $result->saved = true; $result->calendar = $wrapper->toHash(); break; case 'resource': foreach (array('name', 'description', 'response_type') as $key) { $info[$key] = $this->vars->$key; } if (!$calendar_id) { // New resource // @TODO: Groups. if (!$GLOBALS['registry']->isAdmin() && !$GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('resource_management')) { $GLOBALS['notification']->push(_("You are not allowed to create new resources."), 'horde.error'); return $result; } $resource = Kronolith_Resource::addResource(new Kronolith_Resource_Single($info)); } else { try { $rdriver = Kronolith::getDriver('Resource'); $resource = $rdriver->getResource($rdriver->getResourceIdByCalendar($calendar_id)); if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT))) { $GLOBALS['notification']->push(_("You are not allowed to edit this resource."), 'horde.error'); return $result; } foreach (array('name', 'description', 'response_type', 'email') as $key) { $resource->set($key, $this->vars->$key); } $resource->save(); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); return $result; } } $wrapper = new Kronolith_Calendar_Resource(array('resource' => $resource)); $result->calendar = $wrapper->toHash(); $result->saved = true; $result->id = $resource->get('calendar'); $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been saved."), $resource->get('name'), 'horde.success')); break; case 'resourcegroup': if (empty($calendar_id)) { // New resource group. $resource = Kronolith_Resource::addResource( new Kronolith_Resource_Group(array( 'name' => $this->vars->name, 'description' => $this->vars->description, 'members' => $this->vars->members) ) ); } else { $driver = Kronolith::getDriver('Resource'); $resource = $driver->getResource($calendar_id); $resource->set('name', $this->vars->name); $resource->set('description', $this->vars->description); $resource->set('members', $this->vars->members); $resource->save(); } $wrapper = new Kronolith_Calendar_ResourceGroup(array('resource' => $resource)); $result->calendar = $wrapper->toHash(); $result->saved = true; $result->id = $resource->get('calendar'); $GLOBALS['notification']->push(sprintf(_("The resource group \"%s\" has been saved."), $resource->get('name'), 'horde.success')); break; } return $result; } /** * TODO */ public function deleteCalendar() { $calendar_id = $this->vars->calendar; $result = new stdClass; switch ($this->vars->type) { case 'internal': try { $calendar = $GLOBALS['injector']->getInstance('Kronolith_Shares')->getShare($calendar_id); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } try { Kronolith::deleteShare($calendar); } catch (Exception $e) { $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $e->getMessage()), 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success'); break; case 'tasklists': $calendar_id = substr($calendar_id, 6); $tasklists = $GLOBALS['registry']->tasks->listTasklists(true); if (!isset($tasklists[$calendar_id])) { $GLOBALS['notification']->push(_("You are not allowed to delete this task list."), 'horde.error'); return $result; } try { $GLOBALS['registry']->tasks->deleteTasklist($calendar_id); } catch (Exception $e) { $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $tasklists[$calendar_id]->get('name'), $e->getMessage()), 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been deleted."), $tasklists[$calendar_id]->get('name')), 'horde.success'); break; case 'remote': try { $deleted = Kronolith::unsubscribeRemoteCalendar($calendar_id); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $deleted['name'], $deleted['url']), 'horde.success'); break; case 'resource': try { $rdriver = Kronolith::getDriver('Resource'); $resource = $rdriver->getResource($rdriver->getResourceIdByCalendar($calendar_id)); if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE))) { $GLOBALS['notification']->push(_("You are not allowed to delete this resource."), 'horde.error'); return $result; } $name = $resource->get('name'); $rdriver->delete($resource); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been deleted."), $name), 'horde.success'); break; case 'resourcegroup': try { $rdriver = Kronolith::getDriver('Resource'); $resource = $rdriver->getResource($calendar_id); if (!($resource->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE))) { $GLOBALS['notification']->push(_("You are not allowed to delete this resource."), 'horde.error'); return $result; } $name = $resource->get('name'); $rdriver->delete($resource); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push($e->getMessage(), 'horde.error'); return $result; } $GLOBALS['notification']->push(sprintf(_("The resource \"%s\" has been deleted."), $name), 'horde.success'); } $result->deleted = true; return $result; } /** * Returns the information for a shared internal calendar. */ public function getCalendar() { $result = new stdClass; $all_calendars = $GLOBALS['calendar_manager']->get(Kronolith::ALL_CALENDARS); if (!isset($all_calendars[$this->vars->cal]) && !$GLOBALS['conf']['share']['hidden']) { $GLOBALS['notification']->push(_("You are not allowed to view this calendar."), 'horde.error'); return $result; } elseif (!isset($all_calendars[$this->vars->cal])) { // Subscribing to a "hidden" share, check perms. $kronolith_shares = $GLOBALS['injector']->getInstance('Kronolith_Shares'); $share = $kronolith_shares->getShare($this->vars->cal); if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) { $GLOBALS['notification']->push(_("You are not allowed to view this calendar."), 'horde.error'); return $result; } $calendar = new Kronolith_Calendar_Internal(array('share' => $share)); } else { $calendar = $all_calendars[$this->vars->cal]; } $result->calendar = $calendar->toHash(); return $result; } /** * TODO */ public function getRemoteInfo() { $params = array('timeout' => 15); if ($user = $this->vars->user) { $params['user'] = $user; $params['password'] = $this->vars->password; } if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) { $params['proxy'] = $GLOBALS['conf']['http']['proxy']; } $result = new stdClass; try { $driver = $GLOBALS['injector']->getInstance('Kronolith_Factory_Driver')->create('Ical', $params); $driver->open($this->vars->url); if ($driver->isCalDAV()) { $result->success = true; // TODO: find out how to retrieve calendar information via CalDAV. } else { $ical = $driver->getRemoteCalendar(false); $result->success = true; try { $name = $ical->getAttribute('X-WR-CALNAME'); $result->name = $name; } catch (Horde_Icalendar_Exception $e) {} try { $desc = $ical->getAttribute('X-WR-CALDESC'); $result->desc = $desc; } catch (Horde_Icalendar_Exception $e) {} } } catch (Exception $e) { if ($e->getCode() == 401) { $result->auth = true; } else { $GLOBALS['notification']->push($e, 'horde.error'); } } return $result; } /** * TODO */ public function saveCalPref() { return false; } /** * Return a list of available resources. * * @return array A hash of resource_id => resource sorted by resource name. */ public function getResourceList() { $data = array(); $resources = Kronolith::getDriver('Resource') ->listResources(Horde_Perms::READ, array(), 'name'); foreach ($resources as $resource) { $data[] = $resource->toJson(); } return $data; } /** * Handle output of the embedded widget: allows embedding calendar widgets * in external websites. * * The following arguments are required: * - calendar: The share_name for the requested calendar. * - container: The DOM node to populate with the widget. * - view: The view (block) we want. * * The following are optional (and are not used for all views) * - css * - days * - maxevents: The maximum number of events to show. * - months: The number of months to include. */ public function embed() { global $page_output, $registry; /* First, determine the type of view we are asking for */ $view = $this->vars->view; /* The DOM container to put the HTML in on the remote site */ $container = $this->vars->container; /* The share_name of the calendar to display */ $calendar = $this->vars->calendar; /* Deault to showing only 1 month when we have a choice */ $count_month = $this->vars->get('months', 1); /* Default to no limit for the number of events */ $max_events = $this->vars->get('maxevents', 0); /* Default to one week */ $count_days = $this->vars->get('days', 7); if ($this->vars->css == 'none') { $nocss = true; } /* Build the block parameters */ $params = array( 'calendar' => $calendar, 'maxevents' => $max_events, 'months' => $count_month, 'days' => $count_days ); /* Call the Horde_Block api to get the calendar HTML */ $title = $registry->call('horde/blockTitle', array('kronolith', $view, $params)); $results = $registry->call('horde/blockContent', array('kronolith', $view, $params)); /* Some needed paths */ $js_path = $registry->get('jsuri', 'kronolith'); /* Local js */ $jsurl = Horde::url($js_path . '/embed.js', true); /* Horde's js */ $hjs_path = $registry->get('jsuri', 'horde'); $hjsurl = Horde::url($hjs_path . '/tooltips.js', true); $pturl = Horde::url($hjs_path . '/prototype.js', true); /* CSS */ if (empty($nocss)) { $page_output->addThemeStylesheet('embed.css'); Horde::startBuffer(); $page_output->includeStylesheetFiles(array('nobase' => true), true); $css = Horde::endBuffer(); } else { $css = ''; } /* Escape the text and put together the javascript to send back */ $container = Horde_Serialize::serialize($container, Horde_Serialize::JSON); $results = Horde_Serialize::serialize('
' . $title . '
' . $results . '
', Horde_Serialize::JSON); $js = <<'); } if (typeof Horde_ToolTips == 'undefined') { Horde_ToolTips_Autoload = false; document.write(''); } kronolith = new Object(); kronolithNodes = new Array(); document.write(''); document.write('$css'); } kronolithNodes[kronolithNodes.length] = $container; kronolith[$container] = $results; EOT; return new Horde_Core_Ajax_Response_Raw($js, 'text/javascript'); } public function toTimeslice() { $driver = $this->_getDriver($this->vars->cal); $event = $driver->getEvent($this->vars->e); try { Kronolith::toTimeslice($event, $this->vars->t, $this->vars->c); } catch (Kronolith_Exception $e) { $GLOBALS['notification']->push(sprintf(_("Error saving timeslice: %s"), $e->getMessage()), 'horde.error'); return false; } $GLOBALS['notification']->push(_("Successfully saved timeslice."), 'horde.success'); return true; } /** * Check reply status of any resources and report back. Used as a check * before saving an event to give the user feedback. * * The following arguments are expected: * - r: A comma separated string of resource identifiers. * - s: The event start time to check. * - e: The event end time to check. * - u: The event uid, if not a new event. * - c: The event's calendar. */ public function checkResources() { if (empty($GLOBALS['conf']['resource']['driver'])) { return array(); } if ($this->vars->i) { $event = $this->_getDriver($this->vars->c)->getEvent($this->vars->i); } else { $event = Kronolith::getDriver()->getEvent(); } // Overrite start/end times since we may be checking before we edit // an existing event with new times. $event->start = new Horde_Date($this->vars->s); $event->end = new Horde_Date($this->vars->e); $event->start->setTimezone(date_default_timezone_get()); $event->end->setTimezone(date_default_timezone_get()); $results = array(); foreach (explode(',', $this->vars->r) as $id) { $resource = Kronolith::getDriver('Resource')->getResource($id); $results[$id] = $resource->getResponse($event); } return $results; } /** * Returns the driver object for a calendar. * * @param string $cal A calendar string in the format "type|name". * * @return Kronolith_Driver|boolean A driver instance or false on failure. */ protected function _getDriver($cal) { list($driver, $calendar) = explode('|', $cal); if ($driver == 'internal' && !Kronolith::hasPermission($calendar, Horde_Perms::SHOW)) { $GLOBALS['notification']->push(_("Permission Denied"), 'horde.error'); return false; } try { $kronolith_driver = Kronolith::getDriver($driver, $calendar); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); return false; } if ($driver == 'remote') { $kronolith_driver->setParam('timeout', 15); } return $kronolith_driver; } /** * Saves an event and returns a signed result object including the saved * event. * * @param Kronolith_Event $event An event object. * @param Kronolith_Event $original If $event is an exception, this should * be set to the original event. * @param object $attributes The attributes sent by the client. * Expected to contain cstart and cend. * @param boolean $saveOriginal Commit any changes in $original to * storage also. * * @return object The result object. */ protected function _saveEvent(Kronolith_Event $event, Kronolith_Event $original = null, $attributes = null, $saveOriginal = false) { if ($this->vars->targetcalendar) { $cal = $this->vars->targetcalendar; } elseif ($this->vars->cal) { $cal = $this->vars->cal; } else { $cal = $event->calendarType . '|' . $event->calendar; } $result = $this->_signedResponse($cal); $events = array(); try { $event->save(); if (!$this->vars->view_start || !$this->vars->view_end) { $result->events = array(); return $result; } $end = new Horde_Date($this->vars->view_end); $end->hour = 23; $end->min = $end->sec = 59; Kronolith::addEvents( $events, $event, new Horde_Date($this->vars->view_start), $end, true, true); // If this is an exception, we re-add the original event also; // cstart and cend are the cacheStart and cacheEnd dates from the // client. if (!empty($original)) { Kronolith::addEvents( $events, $original, new Horde_Date($attributes->cstart), new Horde_Date($attributes->cend), true, true); if ($saveOriginal) { $original->save(); } } // If this event recurs, we must add any bound exceptions to the // results if ($event->recurs()) { $bound = $event->boundExceptions(false); foreach ($bound as $day => &$exceptions) { foreach ($exceptions as &$exception) { $exception = $exception->toJson(); } } Kronolith::mergeEvents($events, $bound); } $result->events = count($events) ? $events : array(); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); } return $result; } /** * Creates a result object with the signature of the current request. * * @param string $calendar A calendar id. * * @return object The result object. */ protected function _signedResponse($calendar) { $result = new stdClass; $result->cal = $calendar; $result->view = $this->vars->view; $result->sig = $this->vars->sig; return $result; } /** * Add an exception to the original event. * * @param Kronolith_Event $event The recurring event. * @param object $attributes The attributes passed from the client. * Expected to contain either rstart or rday. * * @return Kronolith_Event The event representing the exception, with * the start/end times set the same as the original * occurence. */ protected function _addException(Kronolith_Event $event, $attributes) { if ($attributes->rstart) { $rstart = new Horde_Date($attributes->rstart); $rstart->setTimezone($event->start->timezone); } else { $rstart = new Horde_Date($attributes->rday); $rstart->setTimezone($event->start->timezone); $rstart->hour = $event->start->hour; $rstart->min = $event->start->min; } $event->recurrence->addException($rstart->year, $rstart->month, $rstart->mday); $event->save(); } /** * Creates a new event that represents an exception to a recurring event. * * @param Kronolith_Event $event The original recurring event. * @param Kronolith_Event $copy If present, contains a copy of $event, but * with changes from edited event form. * @param stdClass $attributes The attributes passed from the client. * Expected to contain rstart and rend or * rday that represents the original * starting/ending date of the instance. * * @return Kronolith_Event The event representing the exception */ protected function _copyEvent(Kronolith_Event $event, Kronolith_Event $copy = null, $attributes = null) { if (empty($copy)) { $copy = clone($event); } if ($attributes->rstart) { $rstart = new Horde_Date($attributes->rstart); $rstart->setTimezone($event->start->timezone); $rend = new Horde_Date($attributes->rend); $rend->setTimezone($event->end->timezone); } else { $rstart = new Horde_Date($attributes->rday); $rstart->setTimezone($event->start->timezone); $rstart->hour = $event->start->hour; $rstart->min = $event->start->min; $rend = $rstart->add($event->getDuration); $rend->setTimezone($event->end->timezone); $rend->hour = $event->end->hour; $rend->min = $event->end->min; } $uid = $event->uid; $otime = $event->start->strftime('%T'); // Create new event for the exception $nevent = $event->getDriver()->getEvent(); $nevent->baseid = $uid; $nevent->exceptionoriginaldate = new Horde_Date($rstart->strftime('%Y-%m-%d') . 'T' . $otime); $nevent->exceptionoriginaldate->setTimezone($event->start->timezone); $nevent->creator = $event->creator; $nevent->title = $copy->title; $nevent->description = $copy->description; $nevent->location = $copy->location; $nevent->private = $copy->private; $nevent->url = $copy->url; $nevent->status = $copy->status; $nevent->attendees = $copy->attendees; $nevent->setResources($copy->getResources()); $nevent->start = $rstart; $nevent->end = $rend; $nevent->initialized = true; return $nevent; } }