Files
server/usr/share/psa-horde/kronolith/lib/Ajax/Application/Handler.php
2026-01-07 20:52:11 +01:00

1696 lines
65 KiB
PHP

<?php
/**
* Defines the AJAX actions used in Kronolith.
*
* Copyright 2012-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (GPL). If you
* did not receive this file, see http://www.horde.org/licenses/gpl.
*
* @author Michael Slusarz <slusarz@horde.org>
* @author Jan Schneider <jan@horde.org>
* @author Gonçalo Queirós <mail@goncaloqueiros.net>
* @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:
*<pre>
* -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.
*</pre>
*/
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:
*<pre>
* - 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?
* </pre>
*/
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:
*<pre>
* -email: The attendee's email address.
* -resource: The resource id.
*</pre>
*/
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('<div class="kronolith_embedded"><div class="title">' . $title . '</div>' . $results . '</div>', Horde_Serialize::JSON);
$js = <<<EOT
if (typeof kronolith == 'undefined') {
if (typeof Prototype == 'undefined') {
document.write('<script type="text/javascript" src="$pturl"></script>');
}
if (typeof Horde_ToolTips == 'undefined') {
Horde_ToolTips_Autoload = false;
document.write('<script type="text/javascript" src="$hjsurl"></script>');
}
kronolith = new Object();
kronolithNodes = new Array();
document.write('<script type="text/javascript" src="$jsurl"></script>');
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;
}
}