934 lines
32 KiB
PHP
934 lines
32 KiB
PHP
<?php
|
|
/**
|
|
* The Kronolith_Driver_Sql class implements the Kronolith_Driver API for a
|
|
* SQL backend.
|
|
*
|
|
* Copyright 1999-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 Luc Saillard <luc.saillard@fr.alcove.com>
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @package Kronolith
|
|
*/
|
|
class Kronolith_Driver_Sql extends Kronolith_Driver
|
|
{
|
|
/**
|
|
* The object handle for the current database connection.
|
|
*
|
|
* @var Horde_Db_Adapter
|
|
*/
|
|
protected $_db;
|
|
|
|
/**
|
|
* Column information as Horde_Db_Adapter_Base_Column objects.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_columns = array();
|
|
|
|
/**
|
|
* Cache events as we fetch them to avoid fetching the same event from the
|
|
* DB twice.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_cache = array();
|
|
|
|
/**
|
|
* The class name of the event object to instantiate.
|
|
*
|
|
* Can be overwritten by sub-classes.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_eventClass = 'Kronolith_Event_Sql';
|
|
|
|
/**
|
|
* Returns the background color of the current calendar.
|
|
*
|
|
* @return string The calendar color.
|
|
*/
|
|
public function backgroundColor()
|
|
{
|
|
if ($GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar) !== false) {
|
|
return $GLOBALS['calendar_manager']->getEntry(Kronolith::ALL_CALENDARS, $this->calendar)->background();
|
|
}
|
|
return '#dddddd';
|
|
}
|
|
|
|
/**
|
|
* Returns whether this driver supports per-event timezones.
|
|
*
|
|
* @return boolean Whether this drivers suppports per-event timezones.
|
|
*/
|
|
public function supportsTimezones()
|
|
{
|
|
return $this->getParam('utc');
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param Horde_Date $date The date to list alarms for
|
|
* @param boolean $fullevent Return the full event objects?
|
|
*
|
|
* @return array An array of event ids, or Kronolith_Event objects
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function listAlarms($date, $fullevent = false)
|
|
{
|
|
$allevents = $this->listEvents($date, null, array('has_alarm' => true));
|
|
$events = array();
|
|
foreach ($allevents as $dayevents) {
|
|
foreach ($dayevents as $event) {
|
|
if (!$event->recurs()) {
|
|
$start = new Horde_Date($event->start);
|
|
$start->min -= $event->alarm;
|
|
if ($start->compareDateTime($date) <= 0 &&
|
|
$date->compareDateTime($event->end) <= -1) {
|
|
$events[] = $fullevent ? $event : $event->id;
|
|
}
|
|
} else {
|
|
// Need to start at the beginning of the day to catch the
|
|
// case where we might be within the event's timespan
|
|
// when we call this, hence nextRecurrence() would miss the
|
|
// current event.
|
|
$start = clone $date;
|
|
$start->min = 0;
|
|
$start->hour = 0;
|
|
$start->sec = 0;
|
|
if ($next = $event->recurrence->nextRecurrence($start)) {
|
|
if ($event->recurrence->hasException($next->year, $next->month, $next->mday)) {
|
|
continue;
|
|
}
|
|
$start = new Horde_Date($next);
|
|
$start->min -= $event->alarm;
|
|
$diff = Date_Calc::dateDiff(
|
|
$event->start->mday,
|
|
$event->start->month,
|
|
$event->start->year,
|
|
$event->end->mday,
|
|
$event->end->month,
|
|
$event->end->year
|
|
);
|
|
if ($diff == -1) {
|
|
$diff = 0;
|
|
}
|
|
$end = new Horde_Date(array(
|
|
'year' => $next->year,
|
|
'month' => $next->month,
|
|
'mday' => $next->mday + $diff,
|
|
'hour' => $event->end->hour,
|
|
'min' => $event->end->min,
|
|
'sec' => $event->end->sec)
|
|
);
|
|
if ($start->compareDateTime($date) <= 0 &&
|
|
$date->compareDateTime($end) <= -1) {
|
|
if ($fullevent) {
|
|
$event->start = $next;
|
|
$event->end = $end;
|
|
$events[] = $event;
|
|
} else {
|
|
$events[] = $event->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
/**
|
|
* Searches a calendar.
|
|
*
|
|
* @param object $query An object with the criteria to search for.
|
|
* @param boolean $json Store the results of the events' toJson() method?
|
|
*
|
|
* @return mixed An array of Kronolith_Events.
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function search($query, $json = false)
|
|
{
|
|
/* Build SQL conditions based on the query string. */
|
|
$cond = '((';
|
|
$values = array();
|
|
|
|
foreach (array('title', 'location', 'url', 'description') as $field) {
|
|
if (!empty($query->$field)) {
|
|
$binds = $this->_db->buildClause('event_' . $field, 'LIKE', $this->convertToDriver($query->$field), true);
|
|
if (is_array($binds)) {
|
|
$cond .= $binds[0] . ' AND ';
|
|
$values = array_merge($values, $binds[1]);
|
|
} else {
|
|
$cond .= $binds;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($query->baseid)) {
|
|
$binds = $this->_db->buildClause('event_baseid', '=', $query->baseid, true);
|
|
if (is_array($binds)) {
|
|
$cond .= $binds[0] . ' AND ';
|
|
$values = array_merge($values, $binds[1]);
|
|
} else {
|
|
$cond .= $binds;
|
|
}
|
|
}
|
|
|
|
if (isset($query->status)) {
|
|
$binds = $this->_db->buildClause('event_status', '=', $query->status, true);
|
|
if (is_array($binds)) {
|
|
$cond .= $binds[0] . ' AND ';
|
|
$values = array_merge($values, $binds[1]);
|
|
} else {
|
|
$cond .= $binds;
|
|
}
|
|
}
|
|
if (!empty($query->creator)) {
|
|
$binds = $this->_db->buildClause('event_creator_id', '=', $query->creator, true);
|
|
if (is_array($binds)) {
|
|
$cond .= $binds[0] . ' AND ';
|
|
$values = array_merge($values, $binds[1]);
|
|
} else {
|
|
$cond .= $binds;
|
|
}
|
|
}
|
|
|
|
if ($cond == '((') {
|
|
$cond = '';
|
|
} else {
|
|
$cond = substr($cond, 0, strlen($cond) - 5) . '))';
|
|
}
|
|
|
|
$eventIds = $this->_listEventsConditional(empty($query->start) ? null : $query->start,
|
|
empty($query->end) ? null : $query->end,
|
|
$cond,
|
|
$values);
|
|
$events = array();
|
|
foreach ($eventIds as $eventId) {
|
|
Kronolith::addSearchEvents($events, $this->getEvent($eventId), $query, $json);
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
/**
|
|
* Checks if the event's UID already exists and returns all event
|
|
* ids with that UID.
|
|
*
|
|
* @param string $uid The event's uid.
|
|
* @param string $calendar_id Calendar to search in.
|
|
*
|
|
* @return string|boolean Returns a string with event_id or false if
|
|
* not found.
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function exists($uid, $calendar_id = null)
|
|
{
|
|
$query = 'SELECT event_id FROM ' . $this->_params['table'] . ' WHERE event_uid = ?';
|
|
$values = array($uid);
|
|
|
|
if (!is_null($calendar_id)) {
|
|
$query .= ' AND calendar_id = ?';
|
|
$values[] = $calendar_id;
|
|
}
|
|
|
|
try {
|
|
$event = $this->_db->selectValue($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
return !empty($event) ? $event : false;
|
|
}
|
|
|
|
/**
|
|
* Lists all events in the time range, optionally restricting results to
|
|
* only events with alarms.
|
|
*
|
|
* @param Horde_Date $startDate The start of range date.
|
|
* @param Horde_Date $endDate The end of date range.
|
|
* @param array $options Additional options:
|
|
* - show_recurrence: (boolean) Return every instance of a recurring
|
|
* event?
|
|
* DEFAULT: false (Only return recurring events once
|
|
* inside $startDate - $endDate range)
|
|
* - has_alarm: (boolean) Only return events with alarms.
|
|
* DEFAULT: false (Return all events)
|
|
* - json: (boolean) Store the results of the event's toJson()
|
|
* method?
|
|
* DEFAULT: false
|
|
* - cover_dates: (boolean) Add the events to all days that they
|
|
* cover?
|
|
* DEFAULT: true
|
|
* - hide_exceptions: (boolean) Hide events that represent exceptions to
|
|
* a recurring event.
|
|
* DEFAULT: false (Do not hide exception events)
|
|
* - fetch_tags: (boolean) Fetch tags for all events.
|
|
* DEFAULT: false (Do not fetch event tags)
|
|
*
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
protected function _listEvents(Horde_Date $startDate = null,
|
|
Horde_Date $endDate = null,
|
|
array $options = array())
|
|
{
|
|
if (!is_null($startDate)) {
|
|
$startDate = clone $startDate;
|
|
$startDate->hour = $startDate->min = $startDate->sec = 0;
|
|
}
|
|
if (!is_null($endDate)) {
|
|
$endDate = clone $endDate;
|
|
$endDate->hour = 23;
|
|
$endDate->min = $endDate->sec = 59;
|
|
}
|
|
|
|
$conditions = $options['has_alarm'] ? 'event_alarm > ?' : '';
|
|
$values = $options['has_alarm'] ? array(0) : array();
|
|
if ($options['hide_exceptions']) {
|
|
if (!empty($conditions)) {
|
|
$conditions .= ' AND ';
|
|
}
|
|
$conditions .= "event_baseid = ''";
|
|
}
|
|
|
|
$events = $this->_listEventsConditional($startDate, $endDate, $conditions, $values);
|
|
$results = array();
|
|
$tags = null;
|
|
if ($options['fetch_tags'] && count($events)) {
|
|
$tags = Kronolith::getTagger()->getTags(array_keys($events));
|
|
}
|
|
foreach ($events as $id) {
|
|
$event = $this->getEvent($id);
|
|
if (isset($tags) && !empty($tags[$event->uid])) {
|
|
$event->tags = $tags[$event->uid];
|
|
}
|
|
Kronolith::addEvents(
|
|
$results, $event, $startDate, $endDate, $options['show_recurrence'],
|
|
$options['json'], $options['cover_dates']);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Lists all events that satisfy the given conditions.
|
|
*
|
|
* @param Horde_Date $startInterval Start of range date object.
|
|
* @param Horde_Date $endInterval End of range data object.
|
|
* @param string $conditions Conditions, given as SQL clauses.
|
|
* @param array $vals SQL bind variables for use with
|
|
* $conditions clauses.
|
|
*
|
|
* @return array Events in the given time range satisfying the given
|
|
* conditions.
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
private function _listEventsConditional(Horde_Date $startInterval = null,
|
|
Horde_Date $endInterval = null,
|
|
$conditions = '', array $vals = array())
|
|
{
|
|
if ($this->getParam('utc')) {
|
|
if (!is_null($startInterval)) {
|
|
$startInterval = clone $startInterval;
|
|
$startInterval->setTimezone('UTC');
|
|
}
|
|
if (!is_null($endInterval)) {
|
|
$endInterval = clone $endInterval;
|
|
$endInterval->setTimezone('UTC');
|
|
}
|
|
}
|
|
$q = 'SELECT event_id, event_uid, event_description, event_location,' .
|
|
' event_private, event_status, event_attendees,' .
|
|
' event_title, event_recurcount, event_url, event_timezone,' .
|
|
' event_recurtype, event_recurenddate, event_recurinterval,' .
|
|
' event_recurdays, event_start, event_end, event_allday,' .
|
|
' event_alarm, event_alarm_methods, event_modified,' .
|
|
' event_exceptions, event_creator_id, event_resources, event_baseid,' .
|
|
' event_exceptionoriginaldate FROM ' . $this->_params['table'] .
|
|
' WHERE calendar_id = ?';
|
|
$values = array($this->calendar);
|
|
|
|
if ($conditions) {
|
|
$q .= ' AND ' . $conditions;
|
|
$values = array_merge($values, $vals);
|
|
}
|
|
|
|
if (!is_null($startInterval) && !is_null($endInterval)) {
|
|
$etime = $endInterval->format('Y-m-d H:i:s');
|
|
$stime = $startInterval->format('Y-m-d H:i:s');
|
|
$q .= ' AND ((event_end >= ? AND event_start <= ?) OR (event_recurenddate >= ? AND event_start <= ? AND event_recurtype <> ?))';
|
|
array_push($values, $stime, $etime, $stime, $etime, Horde_Date_Recurrence::RECUR_NONE);
|
|
} elseif (!is_null($startInterval)) {
|
|
$stime = $startInterval->format('Y-m-d H:i:s');
|
|
$q .= ' AND ((event_end >= ?) OR (event_recurenddate >= ? AND event_recurtype <> ?))';
|
|
array_push($values, $stime, $stime, Horde_Date_Recurrence::RECUR_NONE);
|
|
} elseif (!is_null($endInterval)) {
|
|
$q .= ' AND (event_start <= ?)';
|
|
$values[] = $endInterval->format('Y-m-d H:i:s');
|
|
}
|
|
|
|
/* Run the query. */
|
|
try {
|
|
$qr = $this->_db->selectAll($q, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
$events = array();
|
|
foreach ($qr as $row) {
|
|
/* If the event did not have a UID before, we need to give it
|
|
* one. */
|
|
if (empty($row['event_uid'])) {
|
|
$row['event_uid'] = (string)new Horde_Support_Guid;
|
|
|
|
/* Save the new UID for data integrity. */
|
|
$query = 'UPDATE ' . $this->_params['table'] . ' SET event_uid = ? WHERE event_id = ?';
|
|
$values = array($row['event_uid'], $row['event_id']);
|
|
try {
|
|
$this->_db->update($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
}
|
|
|
|
/* Convert TEXT/CLOB fields. */
|
|
$row = $this->convertBlobs($row);
|
|
|
|
/* We have all the information we need to create an event object
|
|
* for this event, so go ahead and cache it. */
|
|
$this->_cache[$this->calendar][$row['event_id']] = new $this->_eventClass($this, $row);
|
|
if ($row['event_recurtype'] == Horde_Date_Recurrence::RECUR_NONE) {
|
|
$events[$row['event_uid']] = $row['event_id'];
|
|
} else {
|
|
$next = $this->nextRecurrence($row['event_id'], $startInterval);
|
|
if ($next &&
|
|
(is_null($endInterval) ||
|
|
$next->compareDateTime($endInterval) < 0)) {
|
|
$events[$row['event_uid']] = $row['event_id'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of events in the current calendar.
|
|
*
|
|
* @return integer The number of events.
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function countEvents()
|
|
{
|
|
$query = sprintf('SELECT count(*) FROM %s WHERE calendar_id = ?',
|
|
$this->_params['table']);
|
|
|
|
/* Run the query. */
|
|
try {
|
|
$result = $this->_db->selectValue($query, array($this->calendar));
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @throws Kronolith_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
*/
|
|
public function getEvent($eventId = null)
|
|
{
|
|
if (!strlen($eventId)) {
|
|
return new $this->_eventClass($this);
|
|
}
|
|
|
|
if (isset($this->_cache[$this->calendar][$eventId])) {
|
|
return $this->_cache[$this->calendar][$eventId];
|
|
}
|
|
|
|
$query = 'SELECT event_id, event_uid, event_description,' .
|
|
' event_location, event_private, event_status, event_attendees,' .
|
|
' event_title, event_recurcount, event_url, event_timezone,' .
|
|
' event_recurtype, event_recurenddate, event_recurinterval,' .
|
|
' event_recurdays, event_start, event_end, event_allday,' .
|
|
' event_alarm, event_alarm_methods, event_modified,' .
|
|
' event_exceptions, event_creator_id, event_resources,' .
|
|
' event_baseid, event_exceptionoriginaldate FROM ' .
|
|
$this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
|
|
|
|
$values = array($eventId, $this->calendar);
|
|
|
|
try {
|
|
$event = $this->_db->selectOne($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
if ($event) {
|
|
/* Convert TEXT/CLOB fields. */
|
|
$event = $this->convertBlobs($event);
|
|
|
|
$this->_cache[$this->calendar][$eventId] = new $this->_eventClass($this, $event);
|
|
return $this->_cache[$this->calendar][$eventId];
|
|
}
|
|
|
|
throw new Horde_Exception_NotFound(_("Event not found"));
|
|
}
|
|
|
|
/**
|
|
* Get an event or events with the given UID value.
|
|
*
|
|
* @param string $uid The UID to match
|
|
* @param array $calendars A restricted array of calendar ids to search
|
|
* @param boolean $getAll Return all matching events?
|
|
*
|
|
* @return Kronolith_Event
|
|
* @throws Kronolith_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
*/
|
|
public function getByUID($uid, $calendars = null, $getAll = false)
|
|
{
|
|
$query = 'SELECT event_id, event_uid, calendar_id, event_description,' .
|
|
' event_location, event_private, event_status, event_attendees,' .
|
|
' event_title, event_recurcount, event_url, event_timezone,' .
|
|
' event_recurtype, event_recurenddate, event_recurinterval,' .
|
|
' event_recurdays, event_start, event_end, event_allday,' .
|
|
' event_alarm, event_alarm_methods, event_modified,' .
|
|
' event_exceptions, event_creator_id, event_resources, event_baseid,' .
|
|
' event_exceptionoriginaldate FROM ' . $this->_params['table'] .
|
|
' WHERE event_uid = ?';
|
|
$values = array((string)$uid);
|
|
|
|
/* Optionally filter by calendar */
|
|
if (!empty($calendars)) {
|
|
if (!count($calendars)) {
|
|
throw new Kronolith_Exception(_("No calendars to search"));
|
|
}
|
|
$query .= ' AND calendar_id IN (?' . str_repeat(', ?', count($calendars) - 1) . ')';
|
|
$values = array_merge($values, $calendars);
|
|
}
|
|
|
|
try {
|
|
$events = $this->_db->selectAll($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
if (!count($events)) {
|
|
throw new Horde_Exception_NotFound(sprintf(_("%s not found"), $uid));
|
|
}
|
|
|
|
$eventArray = array();
|
|
foreach ($events as $event) {
|
|
/* Convert TEXT/CLOB fields. */
|
|
$event = $this->convertBlobs($event);
|
|
|
|
$this->open($event['calendar_id']);
|
|
$this->_cache[$this->calendar][$event['event_id']] = new $this->_eventClass($this, $event);
|
|
$eventArray[] = $this->_cache[$this->calendar][$event['event_id']];
|
|
}
|
|
|
|
if ($getAll) {
|
|
return $eventArray;
|
|
}
|
|
|
|
/* First try the user's own calendars. */
|
|
$ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::READ);
|
|
$event = null;
|
|
foreach ($eventArray as $ev) {
|
|
if (isset($ownerCalendars[$ev->calendar])) {
|
|
$event = $ev;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If not successful, try all calendars the user has access too. */
|
|
if (empty($event)) {
|
|
$readableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::READ);
|
|
foreach ($eventArray as $ev) {
|
|
if (isset($readableCalendars[$ev->calendar])) {
|
|
$event = $ev;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($event)) {
|
|
$event = $eventArray[0];
|
|
}
|
|
|
|
return $event;
|
|
}
|
|
|
|
/**
|
|
* Builds a history hash for a modified event.
|
|
*
|
|
* We don't write it in here because we don't want to commit history before
|
|
* the actual changes are made.
|
|
*
|
|
* @param Kronolith_Event $event The event to log.
|
|
*
|
|
* @return array The change log.
|
|
* @throws Horde_Mime_Exception
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
protected function _buildEventHistory(Kronolith_Event $event)
|
|
{
|
|
$changes = array('action' => 'modify');
|
|
|
|
/* We cannot use getEvent() because of caching. */
|
|
$oldProperties = $this->getbyUID(
|
|
$event->uid,
|
|
array($event->calendar))->toProperties();
|
|
$newProperties = $event->toProperties();
|
|
if (empty($oldProperties)) {
|
|
return $changes;
|
|
}
|
|
|
|
foreach (array_keys($newProperties) as $property) {
|
|
if (!isset($oldProperties[$property]) ||
|
|
($oldProperties[$property] != $newProperties[$property])) {
|
|
$changes['new'][$property] = $newProperties[$property];
|
|
$changes['old'][$property] = !isset($oldProperties[$property]) ? $oldProperties[$property] : null;
|
|
}
|
|
}
|
|
|
|
return $changes;
|
|
}
|
|
|
|
/**
|
|
* Updates an existing event in the backend.
|
|
*
|
|
* @param Kronolith_Event $event The event to save.
|
|
*
|
|
* @return string The event id.
|
|
* @throws Horde_Mime_Exception
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
protected function _updateEvent(Kronolith_Event $event)
|
|
{
|
|
$values = array();
|
|
$query = 'UPDATE ' . $this->_params['table'] . ' SET ';
|
|
foreach ($event->toProperties() as $key => $val) {
|
|
$query .= " $key = ?,";
|
|
$values[] = $val;
|
|
}
|
|
$query = substr($query, 0, -1);
|
|
$query .= ' WHERE event_id = ?';
|
|
$values[] = $event->id;
|
|
|
|
$history = $this->_buildEventHistory($event);
|
|
|
|
try {
|
|
$this->_db->update($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
/* Log the modification of this item in the history log. */
|
|
if ($event->uid) {
|
|
try {
|
|
$GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, $history, true);
|
|
} catch (Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
}
|
|
}
|
|
|
|
/* If this event is an exception, we need to modify the base event's
|
|
* history log also, or some sync clients will never pick up the
|
|
* change. */
|
|
if ($event->baseid) {
|
|
try {
|
|
$GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->baseid, $history, true);
|
|
} catch (Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
}
|
|
}
|
|
$this->_updateTags($event);
|
|
|
|
/* Update Geolocation */
|
|
try {
|
|
$GLOBALS['injector']->getInstance('Kronolith_Geo')->setLocation($event->id, $event->geoLocation);
|
|
} catch (Kronolith_Exception $e) {
|
|
|
|
}
|
|
|
|
/* Notify users about the changed event. */
|
|
$this->_handleNotifications($event, 'edit');
|
|
|
|
return $event->id;
|
|
}
|
|
|
|
/**
|
|
* Adds an event to the backend.
|
|
*
|
|
* @param Kronolith_Event $event The event to save.
|
|
*
|
|
* @return string The event id.
|
|
* @throws Horde_Mime_Exception
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
protected function _addEvent(Kronolith_Event $event)
|
|
{
|
|
if (!$event->id) {
|
|
$event->id = (string)new Horde_Support_Randomid;
|
|
}
|
|
if (!$event->uid) {
|
|
$event->uid = (string)new Horde_Support_Guid;
|
|
}
|
|
|
|
$query = 'INSERT INTO ' . $this->_params['table'];
|
|
$cols_name = ' (event_id, event_uid,';
|
|
$cols_values = ' VALUES (?, ?,';
|
|
$values = array($event->id, $event->uid);
|
|
foreach ($event->toProperties() as $key => $val) {
|
|
$cols_name .= " $key,";
|
|
$cols_values .= ' ?,';
|
|
$values[] = $val;
|
|
}
|
|
$cols_name .= ' calendar_id)';
|
|
$cols_values .= ' ?)';
|
|
$values[] = $this->calendar;
|
|
$query .= $cols_name . $cols_values;
|
|
|
|
try {
|
|
$this->_db->insert($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
/* Log the creation of this item in the history log. */
|
|
try {
|
|
$GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, array('action' => 'add'), true);
|
|
} catch (Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
}
|
|
|
|
$this->_addTags($event);
|
|
|
|
/* Update Geolocation */
|
|
if ($event->geoLocation) {
|
|
try {
|
|
$GLOBALS['injector']->getInstance('Kronolith_Geo')->setLocation($event->id, $event->geoLocation);
|
|
} catch (Kronolith_Exception $e) {
|
|
|
|
}
|
|
}
|
|
|
|
/* Notify users about the new event. */
|
|
$this->_handleNotifications($event, 'add');
|
|
|
|
return $event->id;
|
|
}
|
|
|
|
/**
|
|
* Moves an event to a new calendar.
|
|
*
|
|
* @param string $eventId The event to move.
|
|
* @param string $newCalendar The new calendar.
|
|
*
|
|
* @return Kronolith_Event The old event.
|
|
* @throws Kronolith_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
*/
|
|
protected function _move($eventId, $newCalendar)
|
|
{
|
|
/* Fetch the event for later use. */
|
|
$event = $this->getEvent($eventId);
|
|
|
|
$query = 'UPDATE ' . $this->_params['table'] . ' SET calendar_id = ? WHERE calendar_id = ? AND event_id = ?';
|
|
$values = array($newCalendar, $this->calendar, $eventId);
|
|
|
|
/* Attempt the move query. */
|
|
try {
|
|
$this->_db->update($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
return $event;
|
|
}
|
|
|
|
/**
|
|
* Delete all of a calendar's events.
|
|
*
|
|
* @param string $calendar The name of the calendar to delete.
|
|
*
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function delete($calendar)
|
|
{
|
|
$oldCalendar = $this->calendar;
|
|
$this->open($calendar);
|
|
$events = $this->listEvents(null, null, array('cover_dates' => false, 'hide_exceptions' => true));
|
|
$uids = array();
|
|
foreach ($events as $dayevents) {
|
|
foreach ($dayevents as $event) {
|
|
$uids[] = $event->uid;
|
|
}
|
|
}
|
|
foreach ($uids as $uid) {
|
|
$event = $this->getByUID($uid, array($calendar));
|
|
$this->deleteEvent($event->id);
|
|
}
|
|
|
|
$this->open($oldCalendar);
|
|
}
|
|
|
|
/**
|
|
* Deletes an event.
|
|
*
|
|
* @param string $eventId The ID of the event to delete.
|
|
* @param boolean $silent Don't send notifications, used when deleting
|
|
* events in bulk from maintenance tasks.
|
|
*
|
|
* @throws Kronolith_Exception
|
|
* @throws Horde_Exception_NotFound
|
|
* @throws Horde_Mime_Exception
|
|
*/
|
|
protected function _deleteEvent($eventId, $silent = false)
|
|
{
|
|
global $injector;
|
|
|
|
/* Fetch the event for later use. */
|
|
if ($eventId instanceof Kronolith_Event) {
|
|
$event = $eventId;
|
|
$eventId = $event->id;
|
|
} else {
|
|
$event = $this->getEvent($eventId);
|
|
}
|
|
$original_uid = $event->uid;
|
|
$isRecurring = $event->recurs();
|
|
|
|
$query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
|
|
try {
|
|
$this->_db->delete($query, array($eventId, $this->calendar));
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
/* Notify about the deleted event. */
|
|
if (!$silent) {
|
|
$this->_handleNotifications($event, 'delete');
|
|
}
|
|
|
|
/* Now check for any exceptions that THIS event may have */
|
|
if ($isRecurring) {
|
|
$query = 'SELECT event_id FROM ' . $this->_params['table'] . ' WHERE event_baseid = ? AND calendar_id = ?';
|
|
$values = array($original_uid, $this->calendar);
|
|
|
|
try {
|
|
$result = $this->_db->selectValues($query, $values);
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
foreach ($result as $id) {
|
|
$this->deleteEvent($id, true);
|
|
}
|
|
}
|
|
|
|
/* Delete Geolocation */
|
|
try {
|
|
$injector->getInstance('Kronolith_Geo')->deleteLocation($eventId);
|
|
} catch (Kronolith_Exception $e) { }
|
|
|
|
return $event;
|
|
}
|
|
|
|
/**
|
|
* Filters a list of events to return only those that belong to certain
|
|
* calendars.
|
|
*
|
|
* @param array $uids A list of event UIDs.
|
|
* @param array $calendar A list of calendar IDs.
|
|
*
|
|
* @return array Event UIDs filtered by calendar IDs.
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function filterEventsByCalendar($uids, $calendar)
|
|
{
|
|
$sql = 'SELECT event_uid FROM kronolith_events WHERE calendar_id IN (' . str_repeat('?, ', count($calendar) - 1) . '?) '
|
|
. 'AND event_uid IN (' . str_repeat('?,', count($uids) - 1) . '?)';
|
|
|
|
try {
|
|
$result = $this->_db->selectValues($sql, array_merge($calendar, $uids));
|
|
} catch (Horde_Db_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Attempts to open a connection to the SQL server.
|
|
*
|
|
* @throws Kronolith_Exception
|
|
*/
|
|
public function initialize()
|
|
{
|
|
if (empty($this->_params['db'])) {
|
|
throw new InvalidArgumentException('Missing required Horde_Db_Adapter instance');
|
|
}
|
|
try {
|
|
$this->_db = $this->_params['db'];
|
|
} catch (Horde_Exception $e) {
|
|
throw new Kronolith_Exception($e);
|
|
}
|
|
|
|
$this->_params = array_merge(array(
|
|
'table' => 'kronolith_events'
|
|
), $this->_params);
|
|
|
|
$this->_columns = $this->_db->columns($this->_params['table']);
|
|
}
|
|
|
|
/**
|
|
* Converts a value from the driver's charset to the default
|
|
* charset.
|
|
*
|
|
* @param mixed $value A value to convert.
|
|
*
|
|
* @return mixed The converted value.
|
|
*/
|
|
public function convertFromDriver($value)
|
|
{
|
|
return Horde_String::convertCharset($value, $this->_params['charset'], 'UTF-8');
|
|
}
|
|
|
|
/**
|
|
* Converts a value from the default charset to the driver's
|
|
* charset.
|
|
*
|
|
* @param mixed $value A value to convert.
|
|
*
|
|
* @return mixed The converted value.
|
|
*/
|
|
public function convertToDriver($value)
|
|
{
|
|
return Horde_String::convertCharset($value, 'UTF-8', $this->_params['charset']);
|
|
}
|
|
|
|
/**
|
|
* Converts TEXT/CLOB fields in an event.
|
|
*
|
|
* @param array $event An event hash with TEXT/CLOB columns.
|
|
*
|
|
* @return array The event with TEXT/CLOB columns converted to strings.
|
|
*/
|
|
public function convertBlobs($event)
|
|
{
|
|
$clobs = array(
|
|
'alarm_methods', 'attendees', 'description', 'exceptions',
|
|
'location', 'resources'
|
|
);
|
|
foreach ($clobs as $clob) {
|
|
$event['event_' . $clob] = $this->_columns['event_' . $clob]
|
|
->binaryToString($event['event_' . $clob]);
|
|
}
|
|
return $event;
|
|
}
|
|
}
|