Overview

Packages

  • Kronolith
  • None

Classes

  • Kronolith
  • Kronolith_Ajax_Application
  • Kronolith_Ajax_Imple_ContactAutoCompleter
  • Kronolith_Ajax_Imple_Embed
  • Kronolith_Ajax_Imple_TagActions
  • Kronolith_Ajax_Imple_TagAutoCompleter
  • Kronolith_Api
  • Kronolith_Calendar
  • Kronolith_Calendar_External
  • Kronolith_Calendar_External_Tasks
  • Kronolith_Calendar_Holiday
  • Kronolith_Calendar_Internal
  • Kronolith_Calendar_Remote
  • Kronolith_Calendar_Resource
  • Kronolith_Calendars_Base
  • Kronolith_Calendars_Default
  • Kronolith_Calendars_Kolab
  • Kronolith_Day
  • Kronolith_Driver
  • Kronolith_Driver_Holidays
  • Kronolith_Driver_Horde
  • Kronolith_Driver_Ical
  • Kronolith_Driver_Kolab
  • Kronolith_Driver_Mock
  • Kronolith_Driver_Resource
  • Kronolith_Driver_Sql
  • Kronolith_Event
  • Kronolith_Event_Holidays
  • Kronolith_Event_Horde
  • Kronolith_Event_Ical
  • Kronolith_Event_Kolab
  • Kronolith_Event_Resource
  • Kronolith_Event_Sql
  • Kronolith_Exception
  • Kronolith_Factory_Calendars
  • Kronolith_Factory_Geo
  • Kronolith_Form_CreateCalendar
  • Kronolith_Form_CreateResource
  • Kronolith_Form_CreateResourceGroup
  • Kronolith_Form_DeleteCalendar
  • Kronolith_Form_DeleteResource
  • Kronolith_Form_DeleteResourceGroup
  • Kronolith_Form_EditCalendar
  • Kronolith_Form_EditRemoteCalendar
  • Kronolith_Form_EditResource
  • Kronolith_Form_EditResourceGroup
  • Kronolith_Form_SubscribeRemoteCalendar
  • Kronolith_Form_UnsubscribeRemoteCalendar
  • Kronolith_FreeBusy
  • Kronolith_FreeBusy_View
  • Kronolith_FreeBusy_View_Day
  • Kronolith_FreeBusy_View_Month
  • Kronolith_FreeBusy_View_Week
  • Kronolith_FreeBusy_View_Workweek
  • Kronolith_Geo_Base
  • Kronolith_Geo_Mysql
  • Kronolith_Geo_Sql
  • Kronolith_LoginTasks_SystemTask_Upgrade
  • Kronolith_LoginTasks_Task_PurgeEvents
  • Kronolith_Notification_Listener_AjaxStatus
  • Kronolith_Resource
  • Kronolith_Resource_Base
  • Kronolith_Resource_Group
  • Kronolith_Resource_Single
  • Kronolith_Storage
  • Kronolith_Storage_Kolab
  • Kronolith_Storage_Sql
  • Kronolith_Tagger
  • Kronolith_Test
  • Kronolith_View_Day
  • Kronolith_View_DeleteEvent
  • Kronolith_View_EditEvent
  • Kronolith_View_Event
  • Kronolith_View_ExportEvent
  • Kronolith_View_Month
  • Kronolith_View_Week
  • Kronolith_View_WorkWeek
  • Kronolith_View_Year
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Kronolith_Driver_Sql class implements the Kronolith_Driver API for a
  4:  * SQL backend.
  5:  *
  6:  * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * See the enclosed file COPYING for license information (GPL). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/gpl.
 10:  *
 11:  * @author  Luc Saillard <luc.saillard@fr.alcove.com>
 12:  * @author  Chuck Hagenbuch <chuck@horde.org>
 13:  * @author  Jan Schneider <jan@horde.org>
 14:  * @package Kronolith
 15:  */
 16: class Kronolith_Driver_Sql extends Kronolith_Driver
 17: {
 18:     /**
 19:      * The object handle for the current database connection.
 20:      *
 21:      * @var Horde_Db_Adapter
 22:      */
 23:     protected $_db;
 24: 
 25:     /**
 26:      * Cache events as we fetch them to avoid fetching the same event from the
 27:      * DB twice.
 28:      *
 29:      * @var array
 30:      */
 31:     protected $_cache = array();
 32: 
 33:     /**
 34:      * The class name of the event object to instantiate.
 35:      *
 36:      * Can be overwritten by sub-classes.
 37:      *
 38:      * @var string
 39:      */
 40:     protected $_eventClass = 'Kronolith_Event_Sql';
 41: 
 42:     /**
 43:      * Returns the background color of the current calendar.
 44:      *
 45:      * @return string  The calendar color.
 46:      */
 47:     public function backgroundColor()
 48:     {
 49:         if (isset($GLOBALS['all_calendars'][$this->calendar])) {
 50:             return $GLOBALS['all_calendars'][$this->calendar]->background();
 51:         }
 52:         return '#dddddd';
 53:     }
 54: 
 55:     /**
 56:      *
 57:      * @param Horde_Date $date    The date to list alarms for
 58:      * @param boolean $fullevent  Return the full event objects?
 59:      *
 60:      * @return array  An array of event ids, or Kronolith_Event objects
 61:      * @throws Kronolith_Exception
 62:      */
 63:     public function listAlarms($date, $fullevent = false)
 64:     {
 65:         $allevents = $this->listEvents($date, null, false, true);
 66:         $events = array();
 67:         foreach ($allevents as $dayevents) {
 68:             foreach ($dayevents as $event) {
 69:                 if (!$event->recurs()) {
 70:                     $start = new Horde_Date($event->start);
 71:                     $start->min -= $event->alarm;
 72:                     if ($start->compareDateTime($date) <= 0 &&
 73:                         $date->compareDateTime($event->end) <= -1) {
 74:                         $events[] = $fullevent ? $event : $event->id;
 75:                     }
 76:                 } else {
 77:                     // Need to start at the beginning of the day to catch the
 78:                     // case where we might be within the event's timespan
 79:                     // when we call this, hence nextRecurrence() would miss the
 80:                     // current event.
 81:                     $start = clone $date;
 82:                     $start->min = 0;
 83:                     $start->hour = 0;
 84:                     $start->sec = 0;
 85:                     if ($next = $event->recurrence->nextRecurrence($start)) {
 86:                         if ($event->recurrence->hasException($next->year, $next->month, $next->mday)) {
 87:                             continue;
 88:                         }
 89:                         $start = new Horde_Date($next);
 90:                         $start->min -= $event->alarm;
 91:                         $diff = Date_Calc::dateDiff(
 92:                             $event->start->mday,
 93:                             $event->start->month,
 94:                             $event->start->year,
 95:                             $event->end->mday,
 96:                             $event->end->month,
 97:                             $event->end->year
 98:                         );
 99:                         if ($diff == -1) {
100:                             $diff = 0;
101:                         }
102:                         $end = new Horde_Date(array(
103:                             'year' => $next->year,
104:                             'month' => $next->month,
105:                             'mday' => $next->mday + $diff,
106:                             'hour' => $event->end->hour,
107:                             'min' => $event->end->min,
108:                             'sec' => $event->end->sec)
109:                         );
110:                         if ($start->compareDateTime($date) <= 0 &&
111:                             $date->compareDateTime($end) <= -1) {
112:                             if ($fullevent) {
113:                                 $event->start = $next;
114:                                 $event->end = $end;
115:                                 $events[] = $event;
116:                             } else {
117:                                 $events[] = $event->id;
118:                             }
119:                         }
120:                     }
121:                 }
122:             }
123:         }
124: 
125:         return $events;
126:     }
127: 
128:     /**
129:      * Searches a calendar.
130:      *
131:      * @param object $query  An object with the criteria to search for.
132:      * @param boolean $json  Store the results of the events' toJson() method?
133:      *
134:      * @return mixed  An array of Kronolith_Events.
135:      * @throws Kronolith_Exception
136:      */
137:     public function search($query, $json = false)
138:     {
139:         /* Build SQL conditions based on the query string. */
140:         $cond = '((';
141:         $values = array();
142: 
143:         foreach (array('title', 'location', 'url', 'description') as $field) {
144:             if (!empty($query->$field)) {
145:                 $binds = $this->_db->buildClause('event_' . $field, 'LIKE', $this->convertToDriver($query->$field), true);
146:                 if (is_array($binds)) {
147:                     $cond .= $binds[0] . ' AND ';
148:                     $values = array_merge($values, $binds[1]);
149:                 } else {
150:                     $cond .= $binds;
151:                 }
152:             }
153:         }
154: 
155:         if (!empty($query->baseid)) {
156:             $binds = $this->_db->buildClause('event_baseid', '=', $query->baseid, true);
157:             if (is_array($binds)) {
158:                 $cond .= $binds[0] . ' AND ';
159:                 $values = array_merge($values, $binds[1]);
160:             } else {
161:                 $cond .= $binds;
162:             }
163:         }
164: 
165:         if (isset($query->status)) {
166:             $binds = $this->_db->buildClause('event_status', '=', $query->status, true);
167:             if (is_array($binds)) {
168:                 $cond .= $binds[0] . ' AND ';
169:                 $values = array_merge($values, $binds[1]);
170:             } else {
171:                 $cond .= $binds;
172:             }
173:         }
174:         if (!empty($query->creator)) {
175:             $binds = $this->_db->buildClause('event_creator_id', '=', $query->creator, true);
176:             if (is_array($binds)) {
177:                 $cond .= $binds[0] . ' AND ';
178:                 $values = array_merge($values, $binds[1]);
179:             } else {
180:                 $cond .= $binds;
181:             }
182:         }
183: 
184:         if ($cond == '((') {
185:             $cond = '';
186:         } else {
187:             $cond = substr($cond, 0, strlen($cond) - 5) . '))';
188:         }
189: 
190:         $eventIds = $this->_listEventsConditional(empty($query->start) ? null : $query->start,
191:                                                   empty($query->end) ? null : $query->end,
192:                                                   $cond,
193:                                                   $values);
194:         $events = array();
195:         foreach ($eventIds as $eventId) {
196:             Kronolith::addSearchEvents($events, $this->getEvent($eventId), $query, $json);
197:         }
198: 
199:         return $events;
200:     }
201: 
202:     /**
203:      * Checks if the event's UID already exists and returns all event
204:      * ids with that UID.
205:      *
206:      * @param string $uid          The event's uid.
207:      * @param string $calendar_id  Calendar to search in.
208:      *
209:      * @return string|boolean  Returns a string with event_id or false if
210:      *                         not found.
211:      * @throws Kronolith_Exception
212:      */
213:     public function exists($uid, $calendar_id = null)
214:     {
215:         $query = 'SELECT event_id  FROM ' . $this->_params['table'] . ' WHERE event_uid = ?';
216:         $values = array($uid);
217: 
218:         if (!is_null($calendar_id)) {
219:             $query .= ' AND calendar_id = ?';
220:             $values[] = $calendar_id;
221:         }
222: 
223:         try {
224:             $event = $this->_db->selectValue($query, $values);
225:         } catch (Horde_Db_Exception $e) {
226:             throw new Kronolith_Exception($e);
227:         }
228: 
229:         return !empty($event) ? $event : false;
230:     }
231: 
232:     /**
233:      * Lists all events in the time range, optionally restricting results to
234:      * only events with alarms.
235:      *
236:      * @param Horde_Date $startDate      Start of range date object.
237:      * @param Horde_Date $endDate        End of range data object.
238:      * @param boolean $showRecurrence    Return every instance of a recurring
239:      *                                   event? If false, will only return
240:      *                                   recurring events once inside the
241:      *                                   $startDate - $endDate range.
242:      * @param boolean $hasAlarm          Only return events with alarms?
243:      * @param boolean $json              Store the results of the events'
244:      *                                   toJson() method?
245:      * @param boolean $coverDates        Whether to add the events to all days
246:      *                                   that they cover.
247:      * @param boolean $hideExceptions    Hide events that represent exceptions
248:      *                                   to a recurring event (baseid is set)?
249:      * @param boolean $fetchTags         Whether to fetch tags for all events
250:      *
251:      * @return array  Events in the given time range.
252:      * @throws Kronolith_Exception
253:      */
254:     public function listEvents(Horde_Date $startDate = null,
255:                                Horde_Date $endDate = null,
256:                                $showRecurrence = false, $hasAlarm = false,
257:                                $json = false, $coverDates = true,
258:                                $hideExceptions = false, $fetchTags = false)
259:     {
260:         if (!is_null($startDate)) {
261:             $startDate = clone $startDate;
262:             $startDate->hour = $startDate->min = $startDate->sec = 0;
263:         }
264:         if (!is_null($endDate)) {
265:             $endDate = clone $endDate;
266:             $endDate->hour = 23;
267:             $endDate->min = $endDate->sec = 59;
268:         }
269: 
270:         $conditions =  $hasAlarm ? 'event_alarm > ?' : '';
271:         $values = $hasAlarm ? array(0) : array();
272:         if ($hideExceptions) {
273:             if (!empty($conditions)) {
274:                 $conditions .= ' AND ';
275:             }
276:             $conditions .= "event_baseid = ''";
277:         }
278: 
279:         $events = $this->_listEventsConditional($startDate, $endDate, $conditions, $values);
280:         $results = array();
281:         if ($fetchTags && count($events)) {
282:             $tags = Kronolith::getTagger()->getTags(array_keys($events));
283:         }
284:         foreach ($events as $id) {
285:             $event = $this->getEvent($id);
286:             if (isset($tags) && !empty($tags[$event->uid])) {
287:                 $event->tags = $tags[$event->uid];
288:             }
289:             Kronolith::addEvents($results, $event, $startDate, $endDate,
290:                                  $showRecurrence, $json, $coverDates);
291:         }
292: 
293:         return $results;
294:     }
295: 
296:     /**
297:      * Lists all events that satisfy the given conditions.
298:      *
299:      * @param Horde_Date $startInterval  Start of range date object.
300:      * @param Horde_Date $endInterval    End of range data object.
301:      * @param string $conditions         Conditions, given as SQL clauses.
302:      * @param array $vals                SQL bind variables for use with
303:      *                                   $conditions clauses.
304:      *
305:      * @return array  Events in the given time range satisfying the given
306:      *                conditions.
307:      * @throws Kronolith_Exception
308:      */
309:     private function _listEventsConditional(Horde_Date $startInterval = null,
310:                                             Horde_Date $endInterval = null,
311:                                             $conditions = '', array $vals = array())
312:     {
313:         if ($this->getParam('utc')) {
314:             if (!is_null($startInterval)) {
315:                 $startInterval = clone $startInterval;
316:                 $startInterval->setTimezone('UTC');
317:             }
318:             if (!is_null($endInterval)) {
319:                 $endInterval = clone $endInterval;
320:                 $endInterval->setTimezone('UTC');
321:             }
322:         }
323:         $q = 'SELECT event_id, event_uid, event_description, event_location,' .
324:             ' event_private, event_status, event_attendees,' .
325:             ' event_title, event_recurcount, event_url,' .
326:             ' event_recurtype, event_recurenddate, event_recurinterval,' .
327:             ' event_recurdays, event_start, event_end, event_allday,' .
328:             ' event_alarm, event_alarm_methods, event_modified,' .
329:             ' event_exceptions, event_creator_id, event_resources, event_baseid,' .
330:             ' event_exceptionoriginaldate FROM ' . $this->_params['table'] .
331:             ' WHERE calendar_id = ?';
332:         $values = array($this->calendar);
333: 
334:         if ($conditions) {
335:             $q .= ' AND ' . $conditions;
336:             $values = array_merge($values, $vals);
337:         }
338: 
339:         if (!is_null($startInterval) && !is_null($endInterval)) {
340:             $etime = $endInterval->format('Y-m-d H:i:s');
341:             $stime = $startInterval->format('Y-m-d H:i:s');
342:             $q .= ' AND ((event_end >= ? AND event_start <= ?) OR (event_recurenddate >= ? AND event_start <= ? AND event_recurtype <> ?))';
343:             array_push($values, $stime, $etime, $stime, $etime, Horde_Date_Recurrence::RECUR_NONE);
344:         } elseif (!is_null($startInterval)) {
345:             $stime = $startInterval->format('Y-m-d H:i:s');
346:             $q .= ' AND ((event_end >= ?) OR (event_recurenddate >= ? AND event_recurtype <> ?))';
347:             array_push($values, $stime, $stime, Horde_Date_Recurrence::RECUR_NONE);
348:         } elseif (!is_null($endInterval)) {
349:             $q .= ' AND (event_start <= ?)';
350:             $values[] = $endInterval->format('Y-m-d H:i:s');
351:         }
352: 
353:         /* Run the query. */
354:         try {
355:             $qr = $this->_db->selectAll($q, $values);
356:         } catch (Horde_Db_Exception $e) {
357:             throw new Kronolith_Exception($e);
358:         }
359: 
360:         $events = array();
361:         foreach ($qr as $row) {
362:             /* If the event did not have a UID before, we need to give
363:              * it one. */
364:             if (empty($row['event_uid'])) {
365:                 $row['event_uid'] = (string)new Horde_Support_Guid;
366: 
367:                 /* Save the new UID for data integrity. */
368:                 $query = 'UPDATE ' . $this->_params['table'] . ' SET event_uid = ? WHERE event_id = ?';
369:                 $values = array($row['event_uid'], $row['event_id']);
370:                 try {
371:                     $this->_db->update($query, $values);
372:                 } catch (Horde_Db_Exception $e) {
373:                     throw new Kronolith_Exception($e);
374:                 }
375:             }
376: 
377:             /* We have all the information we need to create an event object
378:              * for this event, so go ahead and cache it. */
379:             $this->_cache[$this->calendar][$row['event_id']] = new $this->_eventClass($this, $row);
380:             if ($row['event_recurtype'] == Horde_Date_Recurrence::RECUR_NONE) {
381:                 $events[$row['event_uid']] = $row['event_id'];
382:             } else {
383:                 $next = $this->nextRecurrence($row['event_id'], $startInterval);
384:                 if ($next &&
385:                     (is_null($endInterval) ||
386:                      $next->compareDateTime($endInterval) < 0)) {
387:                     $events[$row['event_uid']] = $row['event_id'];
388:                 }
389:             }
390:         }
391: 
392:         return $events;
393:     }
394: 
395:     /**
396:      * Returns the number of events in the current calendar.
397:      *
398:      * @return integer  The number of events.
399:      * @throws Kronolith_Exception
400:      */
401:     public function countEvents()
402:     {
403:         $query = sprintf('SELECT count(*) FROM %s WHERE calendar_id = ?',
404:                          $this->_params['table']);
405: 
406:         /* Run the query. */
407:         try {
408:             $result = $this->_db->selectValue($query, array($this->calendar));
409:         } catch (Horde_Db_Exception $e) {
410:             throw new Kronolith_Exception($e);
411:         }
412: 
413:         return $result;
414:     }
415: 
416:     /**
417:      * @throws Kronolith_Exception
418:      * @throws Horde_Exception_NotFound
419:      */
420:     public function getEvent($eventId = null)
421:     {
422:         if (!strlen($eventId)) {
423:             return new $this->_eventClass($this);
424:         }
425: 
426:         if (isset($this->_cache[$this->calendar][$eventId])) {
427:             return $this->_cache[$this->calendar][$eventId];
428:         }
429: 
430:         $query = 'SELECT event_id, event_uid, event_description,' .
431:             ' event_location, event_private, event_status, event_attendees,' .
432:             ' event_title, event_recurcount, event_url,' .
433:             ' event_recurtype, event_recurenddate, event_recurinterval,' .
434:             ' event_recurdays, event_start, event_end, event_allday,' .
435:             ' event_alarm, event_alarm_methods, event_modified,' .
436:             ' event_exceptions, event_creator_id, event_resources,' .
437:             ' event_baseid, event_exceptionoriginaldate FROM ' .
438:             $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
439: 
440:         $values = array($eventId, $this->calendar);
441: 
442:         try {
443:             $event = $this->_db->selectOne($query, $values);
444:         } catch (Horde_Db_Exception $e) {
445:             throw new Kronolith_Exception($e);
446:         }
447:         if ($event) {
448:             $this->_cache[$this->calendar][$eventId] = new $this->_eventClass($this, $event);
449:             return $this->_cache[$this->calendar][$eventId];
450:         }
451: 
452:         throw new Horde_Exception_NotFound(_("Event not found"));
453:     }
454: 
455:     /**
456:      * Get an event or events with the given UID value.
457:      *
458:      * @param string $uid       The UID to match
459:      * @param array $calendars  A restricted array of calendar ids to search
460:      * @param boolean $getAll   Return all matching events?
461:      *
462:      * @return Kronolith_Event
463:      * @throws Kronolith_Exception
464:      * @throws Horde_Exception_NotFound
465:      */
466:     public function getByUID($uid, $calendars = null, $getAll = false)
467:     {
468:         $query = 'SELECT event_id, event_uid, calendar_id, event_description,' .
469:             ' event_location, event_private, event_status, event_attendees,' .
470:             ' event_title, event_recurcount, event_url,' .
471:             ' event_recurtype, event_recurenddate, event_recurinterval,' .
472:             ' event_recurdays, event_start, event_end, event_allday,' .
473:             ' event_alarm, event_alarm_methods, event_modified,' .
474:             ' event_exceptions, event_creator_id, event_resources, event_baseid,' .
475:             ' event_exceptionoriginaldate FROM ' . $this->_params['table'] .
476:             ' WHERE event_uid = ?';
477:         $values = array((string)$uid);
478: 
479:         /* Optionally filter by calendar */
480:         if (!is_null($calendars)) {
481:             if (!count($calendars)) {
482:                 throw new Kronolith_Exception(_("No calendars to search"));
483:             }
484:             $query .= ' AND calendar_id IN (?' . str_repeat(', ?', count($calendars) - 1) . ')';
485:             $values = array_merge($values, $calendars);
486:         }
487: 
488:         try {
489:             $events = $this->_db->selectAll($query, $values);
490:         } catch (Horde_Db_Exception $e) {
491:             throw new Kronolith_Exception($e);
492:         }
493:         if (!count($events)) {
494:             throw new Horde_Exception_NotFound($uid . ' not found');
495:         }
496: 
497:         $eventArray = array();
498:         foreach ($events as $event) {
499:             $this->open($event['calendar_id']);
500:             $this->_cache[$this->calendar][$event['event_id']] = new $this->_eventClass($this, $event);
501:             $eventArray[] = $this->_cache[$this->calendar][$event['event_id']];
502:         }
503: 
504:         if ($getAll) {
505:             return $eventArray;
506:         }
507: 
508:         /* First try the user's own calendars. */
509:         $ownerCalendars = Kronolith::listInternalCalendars(true, Horde_Perms::READ);
510:         $event = null;
511:         foreach ($eventArray as $ev) {
512:             if (isset($ownerCalendars[$ev->calendar])) {
513:                 $event = $ev;
514:                 break;
515:             }
516:         }
517: 
518:         /* If not successful, try all calendars the user has access too. */
519:         if (empty($event)) {
520:             $readableCalendars = Kronolith::listInternalCalendars(false, Horde_Perms::READ);
521:             foreach ($eventArray as $ev) {
522:                 if (isset($readableCalendars[$ev->calendar])) {
523:                     $event = $ev;
524:                     break;
525:                 }
526:             }
527:         }
528: 
529:         if (empty($event)) {
530:             $event = $eventArray[0];
531:         }
532: 
533:         return $event;
534:     }
535: 
536:     /**
537:      * Updates an existing event in the backend.
538:      *
539:      * @param Kronolith_Event $event  The event to save.
540:      *
541:      * @return string  The event id.
542:      * @throws Horde_Mime_Exception
543:      * @throws Kronolith_Exception
544:      */
545:     protected function _updateEvent(Kronolith_Event $event)
546:     {
547:         $values = array();
548:         $query = 'UPDATE ' . $this->_params['table'] . ' SET ';
549:         foreach ($event->toProperties() as $key => $val) {
550:             $query .= " $key = ?,";
551:             $values[] = $val;
552:         }
553:         $query = substr($query, 0, -1);
554:         $query .= ' WHERE event_id = ?';
555:         $values[] = $event->id;
556: 
557:         try {
558:             $result = $this->_db->update($query, $values);
559:         } catch (Horde_Db_Exception $e) {
560:             throw new Kronolith_Exception($e);
561:         }
562: 
563:         /* Log the modification of this item in the history log. */
564:         if ($event->uid) {
565:             try {
566:                 $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, array('action' => 'modify'), true);
567:             } catch (Exception $e) {
568:                 Horde::logMessage($e, 'ERR');
569:             }
570:         }
571: 
572:         /* If this event is an exception, we need to modify the base event's
573:          * history log also, or some sync clients will never pick up the
574:          * change. */
575:         if ($event->baseid) {
576:             try {
577:                 $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->baseid, array('action' => 'modify'), true);
578:             } catch (Exception $e) {
579:                 Horde::logMessage($e, 'ERR');
580:             }
581:         }
582:         $this->_updateTags($event);
583: 
584:         /* Update Geolocation */
585:         try {
586:             $GLOBALS['injector']->getInstance('Kronolith_Geo')->setLocation($event->id, $event->geoLocation);
587:         } catch (Kronolith_Exception $e) {
588: 
589:         }
590: 
591:         /* Notify users about the changed event. */
592:         $this->_handleNotifications($event, 'edit');
593: 
594:         return $event->id;
595:     }
596: 
597:     /**
598:      * Adds an event to the backend.
599:      *
600:      * @param Kronolith_Event $event  The event to save.
601:      *
602:      * @return string  The event id.
603:      * @throws Horde_Mime_Exception
604:      * @throws Kronolith_Exception
605:      */
606:     protected function _addEvent(Kronolith_Event $event)
607:     {
608:         if (!$event->id) {
609:             $event->id = (string)new Horde_Support_Randomid;
610:         }
611:         if (!$event->uid) {
612:             $event->uid = (string)new Horde_Support_Guid;
613:         }
614: 
615:         $query = 'INSERT INTO ' . $this->_params['table'];
616:         $cols_name = ' (event_id, event_uid,';
617:         $cols_values = ' VALUES (?, ?,';
618:         $values = array($event->id, $event->uid);
619:         foreach ($event->toProperties() as $key => $val) {
620:             $cols_name .= " $key,";
621:             $cols_values .= ' ?,';
622:             $values[] = $val;
623:         }
624:         $cols_name .= ' calendar_id)';
625:         $cols_values .= ' ?)';
626:         $values[] = $this->calendar;
627:         $query .= $cols_name . $cols_values;
628: 
629:         try {
630:             $result = $this->_db->insert($query, $values);
631:         } catch (Horde_Db_Exception $e) {
632:             throw new Kronolith_Exception($e);
633:         }
634:         /* Log the creation of this item in the history log. */
635:         try {
636:             $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, array('action' => 'add'), true);
637:         } catch (Exception $e) {
638:             Horde::logMessage($e, 'ERR');
639:         }
640: 
641:         $this->_addTags($event);
642: 
643:         /* Update Geolocation */
644:         if ($event->geoLocation) {
645:             try {
646:                 $GLOBALS['injector']->getInstance('Kronolith_Geo')->setLocation($event->id, $event->geoLocation);
647:             } catch (Kronolith_Exception $e) {
648: 
649:             }
650:         }
651: 
652:         /* Notify users about the new event. */
653:         $this->_handleNotifications($event, 'add');
654: 
655:         return $event->id;
656:     }
657: 
658:     /**
659:      * Helper function to update an existing event's tags to tagger storage.
660:      *
661:      * @param Kronolith_Event $event  The event to update
662:      */
663:     protected function _updateTags(Kronolith_Event $event)
664:     {
665:         /* Update tags */
666:         Kronolith::getTagger()->replaceTags($event->uid, $event->tags, $event->creator, 'event');
667: 
668:         /* Add tags again, but as the share owner (replaceTags removes ALL tags). */
669:         try {
670:             $cal = $GLOBALS['kronolith_shares']->getShare($event->calendar);
671:         } catch (Horde_Share_Exception $e) {
672:             throw new Kronolith_Exception($e);
673:         }
674:         Kronolith::getTagger()->tag($event->uid, $event->tags, $cal->get('owner'), 'event');
675:     }
676: 
677:     /**
678:      * Helper function to add tags from a newly creted event to the tagger.
679:      *
680:      * @param Kronolith_Event $event  The event to save tags to storage for.
681:      */
682:     protected function _addTags(Kronolith_Event $event)
683:     {
684:         /* Deal with any tags */
685:         $tagger = Kronolith::getTagger();
686:         $tagger->tag($event->uid, $event->tags, $event->creator, 'event');
687: 
688:         /* Add tags again, but as the share owner (replaceTags removes ALL
689:          * tags). */
690:         try {
691:             $cal = $GLOBALS['kronolith_shares']->getShare($event->calendar);
692:         } catch (Horde_Share_Exception $e) {
693:             Horde::logMessage($e->getMessage(), 'ERR');
694:             throw new Kronolith_Exception($e);
695:         }
696: 
697:         if ($cal->get('owner') != $event->creator) {
698:             $tagger->tag($event->uid, $event->tags, $cal->get('owner'), 'event');
699:         }
700:     }
701: 
702:     /**
703:      * Wrapper for sending notifications, so that we can overwrite this action
704:      * in Kronolith_Driver_Resource.
705:      *
706:      * @param Kronolith_Event $event
707:      * @param string $action
708:      */
709:     protected function _handleNotifications(Kronolith_Event $event, $action)
710:     {
711:         Kronolith::sendNotification($event, $action);
712:     }
713: 
714:     /**
715:      * Moves an event to a new calendar.
716:      *
717:      * @param string $eventId      The event to move.
718:      * @param string $newCalendar  The new calendar.
719:      *
720:      * @return Kronolith_Event  The old event.
721:      * @throws Kronolith_Exception
722:      * @throws Horde_Exception_NotFound
723:      */
724:     protected function _move($eventId, $newCalendar)
725:     {
726:         /* Fetch the event for later use. */
727:         $event = $this->getEvent($eventId);
728: 
729:         $query = 'UPDATE ' . $this->_params['table'] . ' SET calendar_id = ? WHERE calendar_id = ? AND event_id = ?';
730:         $values = array($newCalendar, $this->calendar, $eventId);
731: 
732:         /* Attempt the move query. */
733:         try {
734:             $result = $this->_db->update($query, $values);
735:         } catch (Horde_Db_Exception $e) {
736:             throw new Kronolith_Exception($e);
737:         }
738: 
739:         return $event;
740:     }
741: 
742:     /**
743:      * Delete a calendar and all its events.
744:      *
745:      * @param string $calendar  The name of the calendar to delete.
746:      *
747:      * @throws Kronolith_Exception
748:      */
749:     public function delete($calendar)
750:     {
751:         $oldCalendar = $this->calendar;
752:         $this->open($calendar);
753:         $events = $this->listEvents(null, null, false, false, false);
754:         $uids = array();
755:         foreach ($events as $dayevents) {
756:             foreach ($dayevents as $event) {
757:                 $uids[] = $event->uid;
758:             }
759:         }
760:         foreach ($uids as $uid) {
761:             $event = $this->getByUID($uid, array($calendar));
762:             try {
763:                 $this->deleteEvent($event->id);
764:             } catch (Kronolith_Exception $e) {
765:                 Horde::logMessage($e, 'ERR');
766:             }
767:         }
768: 
769:         $this->open($oldCalendar);
770:     }
771: 
772:     /**
773:      * Delete an event.
774:      *
775:      * @param string $eventId  The ID of the event to delete.
776:      * @param boolean $silent  Don't send notifications, used when deleting
777:      *                         events in bulk from maintenance tasks.
778:      *
779:      * @throws Kronolith_Exception
780:      * @throws Horde_Exception_NotFound
781:      * @throws Horde_Mime_Exception
782:      */
783:     public function deleteEvent($eventId, $silent = false)
784:     {
785:         /* Fetch the event for later use. */
786:         $event = $this->getEvent($eventId);
787:         $original_uid = $event->uid;
788:         $isRecurring = $event->recurs();
789: 
790:         $query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE event_id = ? AND calendar_id = ?';
791:         try {
792:             $this->_db->delete($query, array($eventId, $this->calendar));
793:         } catch (Horde_Db_Exception $e) {
794:             throw new Kronolith_Exception($e);
795:         }
796:         /* Log the deletion of this item in the history log. */
797:         if ($event->uid) {
798:             try {
799:                 $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->uid, array('action' => 'delete'), true);
800:             } catch (Exception $e) {
801:                 Horde::logMessage($e, 'ERR');
802:             }
803:         }
804: 
805:         /* Remove the event from any resources that are attached to it */
806:         $resources = $event->getResources();
807:         if (count($resources)) {
808:             $rd = Kronolith::getDriver('Resource');
809:             foreach ($resources as $uid => $resource) {
810:                 if ($resource['response'] !== Kronolith::RESPONSE_DECLINED) {
811:                     $r = $rd->getResource($uid);
812:                     $r->removeEvent($event);
813:                 }
814:             }
815:         }
816: 
817:         /* Remove any pending alarms. */
818:         $GLOBALS['injector']->getInstance('Horde_Alarm')->delete($event->uid);
819: 
820:         /* Remove any tags */
821:         $tagger = Kronolith::getTagger();
822:         $tagger->replaceTags($event->uid, array(), $event->creator, 'event');
823: 
824:         /* Remove any geolocation data */
825:         try {
826:             $GLOBALS['injector']->getInstance('Kronolith_Geo')->deleteLocation($event->id);
827:         } catch (Kronolith_Exception $e) {
828:         }
829: 
830:         /* Notify about the deleted event. */
831:         if (!$silent) {
832:             $this->_handleNotifications($event, 'delete');
833:         }
834: 
835:         /* See if this event represents an exception - if so, touch the base
836:          * event's history. The $isRecurring check is to prevent an infinite
837:          * loop in the off chance that an exception is entered as a recurring
838:          * event.
839:          */
840:         if ($event->baseid && !$isRecurring) {
841:             try {
842:                 $GLOBALS['injector']->getInstance('Horde_History')->log('kronolith:' . $this->calendar . ':' . $event->baseid, array('action' => 'modify'), true);
843:             } catch (Exception $e) {
844:                 Horde::logMessage($e, 'ERR');
845:             }
846:         }
847: 
848:         /* Now check for any exceptions that THIS event may have */
849:         if ($isRecurring) {
850:             $query = 'SELECT event_id FROM ' . $this->_params['table'] . ' WHERE event_baseid = ? AND calendar_id = ?';
851:             $values = array($original_uid, $this->calendar);
852: 
853:             try {
854:                 $result = $this->_db->selectValues($query, $values);
855:             } catch (Horde_Db_Exception $e) {
856:                 throw new Kronolith_Exception($e);
857:             }
858:             foreach ($result as $id) {
859:                 $this->deleteEvent($id, $silent);
860:             }
861:         }
862:     }
863: 
864:     /**
865:      * Filters a list of events to return only those that belong to certain
866:      * calendars.
867:      *
868:      * @param array $uids      A list of event UIDs.
869:      * @param array $calendar  A list of calendar IDs.
870:      *
871:      * @return array  Event UIDs filtered by calendar IDs.
872:      * @throws Kronolith_Exception
873:      */
874:     public function filterEventsByCalendar($uids, $calendar)
875:     {
876:         $sql = 'SELECT event_uid FROM kronolith_events WHERE calendar_id IN (' . str_repeat('?, ', count($calendar) - 1) . '?) '
877:             . 'AND event_uid IN (' . str_repeat('?,', count($uids) - 1) . '?)';
878: 
879:         try {
880:             $result = $this->_db->selectValues($sql, array_merge($calendar, $uids));
881:         } catch (Horde_Db_Exception $e) {
882:             throw new Kronolith_Exception($e);
883:         }
884: 
885:         return $result;
886:     }
887: 
888:     /**
889:      * Attempts to open a connection to the SQL server.
890:      *
891:      * @throws Kronolith_Exception
892:      */
893:     public function initialize()
894:     {
895:         if (empty($this->_params['db'])) {
896:             throw new InvalidArgumentException('Missing required Horde_Db_Adapter instance');
897:         }
898:         try {
899:             $this->_db = $this->_params['db'];
900:         } catch (Horde_Exception $e) {
901:             throw new Kronolith_Exception($e);
902:         }
903: 
904:         /* Handle any database specific initialization code to run. */
905:         try {
906:             switch ($this->_db->adapterName()) {
907:             case 'Oracle':
908:                 $query = "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'";
909:                 $this->_db->execute($query);
910:                 break;
911: 
912:             case 'PDO_PostgreSQL':
913:                 $query = "SET datestyle TO 'iso'";
914:                 $this->_db->execute($query);
915:                 break;
916:             }
917: 
918:         } catch (Horde_Db_Exception $e) {
919:             throw new Kronolith_Exception($e);
920:         }
921:         $this->_params = array_merge(array(
922:             'table' => 'kronolith_events'
923:         ), $this->_params);
924:     }
925: 
926:     /**
927:      * Converts a value from the driver's charset to the default
928:      * charset.
929:      *
930:      * @param mixed $value  A value to convert.
931:      *
932:      * @return mixed  The converted value.
933:      */
934:     public function convertFromDriver($value)
935:     {
936:         return Horde_String::convertCharset($value, $this->_params['charset'], 'UTF-8');
937:     }
938: 
939:     /**
940:      * Converts a value from the default charset to the driver's
941:      * charset.
942:      *
943:      * @param mixed $value  A value to convert.
944:      *
945:      * @return mixed  The converted value.
946:      */
947:     public function convertToDriver($value)
948:     {
949:         return Horde_String::convertCharset($value, 'UTF-8', $this->_params['charset']);
950:     }
951: 
952: }
953: 
API documentation generated by ApiGen